// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/renderer/extensions/cast_streaming_native_handler.h" #include #include #include "base/logging.h" #include "base/message_loop/message_loop.h" #include "base/strings/string_number_conversions.h" #include "chrome/common/extensions/api/cast_streaming_rtp_stream.h" #include "chrome/common/extensions/api/cast_streaming_udp_transport.h" #include "chrome/renderer/media/cast_rtp_stream.h" #include "chrome/renderer/media/cast_session.h" #include "chrome/renderer/media/cast_udp_transport.h" #include "content/public/renderer/v8_value_converter.h" #include "extensions/renderer/script_context.h" #include "net/base/host_port_pair.h" #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h" #include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h" using content::V8ValueConverter; // Extension types. using extensions::api::cast_streaming_rtp_stream::CodecSpecificParams; using extensions::api::cast_streaming_rtp_stream::RtpParams; using extensions::api::cast_streaming_rtp_stream::RtpPayloadParams; using extensions::api::cast_streaming_udp_transport::IPEndPoint; namespace extensions { namespace { const char kRtpStreamNotFound[] = "The RTP stream cannot be found"; const char kUdpTransportNotFound[] = "The UDP transport cannot be found"; const char kInvalidDestination[] = "Invalid destination"; const char kInvalidRtpParams[] = "Invalid value for RTP params"; const char kInvalidAesKey[] = "Invalid value for AES key"; const char kInvalidAesIvMask[] = "Invalid value for AES IV mask"; const char kInvalidStreamArgs[] = "Invalid stream arguments"; const char kUnableToConvertArgs[] = "Unable to convert arguments"; const char kUnableToConvertParams[] = "Unable to convert params"; // These helper methods are used to convert between Extension API // types and Cast types. void ToCastCodecSpecificParams(const CodecSpecificParams& ext_params, CastCodecSpecificParams* cast_params) { cast_params->key = ext_params.key; cast_params->value = ext_params.value; } void FromCastCodecSpecificParams(const CastCodecSpecificParams& cast_params, CodecSpecificParams* ext_params) { ext_params->key = cast_params.key; ext_params->value = cast_params.value; } namespace { bool HexDecode(const std::string& input, std::string* output) { std::vector bytes; if (!base::HexStringToBytes(input, &bytes)) return false; output->assign(reinterpret_cast(&bytes[0]), bytes.size()); return true; } } // namespace bool ToCastRtpPayloadParamsOrThrow(v8::Isolate* isolate, const RtpPayloadParams& ext_params, CastRtpPayloadParams* cast_params) { cast_params->payload_type = ext_params.payload_type; cast_params->max_latency_ms = ext_params.max_latency; cast_params->min_latency_ms = ext_params.min_latency ? *ext_params.min_latency : ext_params.max_latency; cast_params->codec_name = ext_params.codec_name; cast_params->ssrc = ext_params.ssrc; cast_params->feedback_ssrc = ext_params.feedback_ssrc; cast_params->clock_rate = ext_params.clock_rate ? *ext_params.clock_rate : 0; cast_params->min_bitrate = ext_params.min_bitrate ? *ext_params.min_bitrate : 0; cast_params->max_bitrate = ext_params.max_bitrate ? *ext_params.max_bitrate : 0; cast_params->channels = ext_params.channels ? *ext_params.channels : 0; cast_params->max_frame_rate = ext_params.max_frame_rate ? *ext_params.max_frame_rate : 0.0; cast_params->width = ext_params.width ? *ext_params.width : 0; cast_params->height = ext_params.height ? *ext_params.height : 0; if (ext_params.aes_key && !HexDecode(*ext_params.aes_key, &cast_params->aes_key)) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidAesKey))); return false; } if (ext_params.aes_iv_mask && !HexDecode(*ext_params.aes_iv_mask, &cast_params->aes_iv_mask)) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidAesIvMask))); return false; } for (size_t i = 0; i < ext_params.codec_specific_params.size(); ++i) { CastCodecSpecificParams cast_codec_params; ToCastCodecSpecificParams(*ext_params.codec_specific_params[i], &cast_codec_params); cast_params->codec_specific_params.push_back(cast_codec_params); } return true; } void FromCastRtpPayloadParams(const CastRtpPayloadParams& cast_params, RtpPayloadParams* ext_params) { ext_params->payload_type = cast_params.payload_type; ext_params->max_latency = cast_params.max_latency_ms; ext_params->min_latency.reset(new int(cast_params.min_latency_ms)); ext_params->codec_name = cast_params.codec_name; ext_params->ssrc = cast_params.ssrc; ext_params->feedback_ssrc = cast_params.feedback_ssrc; if (cast_params.clock_rate) ext_params->clock_rate.reset(new int(cast_params.clock_rate)); if (cast_params.min_bitrate) ext_params->min_bitrate.reset(new int(cast_params.min_bitrate)); if (cast_params.max_bitrate) ext_params->max_bitrate.reset(new int(cast_params.max_bitrate)); if (cast_params.channels) ext_params->channels.reset(new int(cast_params.channels)); if (cast_params.max_frame_rate > 0.0) ext_params->max_frame_rate.reset(new double(cast_params.max_frame_rate)); if (cast_params.width) ext_params->width.reset(new int(cast_params.width)); if (cast_params.height) ext_params->height.reset(new int(cast_params.height)); for (size_t i = 0; i < cast_params.codec_specific_params.size(); ++i) { linked_ptr ext_codec_params( new CodecSpecificParams()); FromCastCodecSpecificParams(cast_params.codec_specific_params[i], ext_codec_params.get()); ext_params->codec_specific_params.push_back(ext_codec_params); } } void FromCastRtpParams(const CastRtpParams& cast_params, RtpParams* ext_params) { std::copy(cast_params.rtcp_features.begin(), cast_params.rtcp_features.end(), std::back_inserter(ext_params->rtcp_features)); FromCastRtpPayloadParams(cast_params.payload, &ext_params->payload); } bool ToCastRtpParamsOrThrow(v8::Isolate* isolate, const RtpParams& ext_params, CastRtpParams* cast_params) { std::copy(ext_params.rtcp_features.begin(), ext_params.rtcp_features.end(), std::back_inserter(cast_params->rtcp_features)); if (!ToCastRtpPayloadParamsOrThrow(isolate, ext_params.payload, &cast_params->payload)) { return false; } return true; } } // namespace CastStreamingNativeHandler::CastStreamingNativeHandler(ScriptContext* context) : ObjectBackedNativeHandler(context), last_transport_id_(1), weak_factory_(this) { RouteFunction("CreateSession", base::Bind(&CastStreamingNativeHandler::CreateCastSession, base::Unretained(this))); RouteFunction("DestroyCastRtpStream", base::Bind(&CastStreamingNativeHandler::DestroyCastRtpStream, base::Unretained(this))); RouteFunction("GetSupportedParamsCastRtpStream", base::Bind(&CastStreamingNativeHandler::GetSupportedParamsCastRtpStream, base::Unretained(this))); RouteFunction("StartCastRtpStream", base::Bind(&CastStreamingNativeHandler::StartCastRtpStream, base::Unretained(this))); RouteFunction("StopCastRtpStream", base::Bind(&CastStreamingNativeHandler::StopCastRtpStream, base::Unretained(this))); RouteFunction("DestroyCastUdpTransport", base::Bind(&CastStreamingNativeHandler::DestroyCastUdpTransport, base::Unretained(this))); RouteFunction("SetDestinationCastUdpTransport", base::Bind(&CastStreamingNativeHandler::SetDestinationCastUdpTransport, base::Unretained(this))); RouteFunction("SetOptionsCastUdpTransport", base::Bind(&CastStreamingNativeHandler::SetOptionsCastUdpTransport, base::Unretained(this))); RouteFunction("ToggleLogging", base::Bind(&CastStreamingNativeHandler::ToggleLogging, base::Unretained(this))); RouteFunction("GetRawEvents", base::Bind(&CastStreamingNativeHandler::GetRawEvents, base::Unretained(this))); RouteFunction("GetStats", base::Bind(&CastStreamingNativeHandler::GetStats, base::Unretained(this))); } CastStreamingNativeHandler::~CastStreamingNativeHandler() { } void CastStreamingNativeHandler::CreateCastSession( const v8::FunctionCallbackInfo& args) { CHECK_EQ(3, args.Length()); CHECK(args[2]->IsFunction()); v8::Isolate* isolate = context()->v8_context()->GetIsolate(); if ((args[0]->IsNull() || args[0]->IsUndefined()) && (args[1]->IsNull() || args[1]->IsUndefined())) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidStreamArgs))); return; } scoped_refptr session(new CastSession()); scoped_ptr stream1, stream2; if (!args[0]->IsNull() && !args[0]->IsUndefined()) { CHECK(args[0]->IsObject()); blink::WebDOMMediaStreamTrack track = blink::WebDOMMediaStreamTrack::fromV8Value(args[0]); if (track.isNull()) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidStreamArgs))); return; } stream1.reset(new CastRtpStream(track.component(), session)); } if (!args[1]->IsNull() && !args[1]->IsUndefined()) { CHECK(args[1]->IsObject()); blink::WebDOMMediaStreamTrack track = blink::WebDOMMediaStreamTrack::fromV8Value(args[1]); if (track.isNull()) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidStreamArgs))); return; } stream2.reset(new CastRtpStream(track.component(), session)); } scoped_ptr udp_transport( new CastUdpTransport(session)); // TODO(imcheng): Use a weak reference to ensure we don't call into an // invalid context when the callback is invoked. create_callback_.reset(args[2].As()); base::MessageLoop::current()->PostTask( FROM_HERE, base::Bind( &CastStreamingNativeHandler::CallCreateCallback, weak_factory_.GetWeakPtr(), base::Passed(&stream1), base::Passed(&stream2), base::Passed(&udp_transport))); } void CastStreamingNativeHandler::CallCreateCallback( scoped_ptr stream1, scoped_ptr stream2, scoped_ptr udp_transport) { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); v8::Handle callback_args[3]; callback_args[0] = v8::Null(isolate); callback_args[1] = v8::Null(isolate); if (stream1) { const int stream1_id = last_transport_id_++; callback_args[0] = v8::Integer::New(isolate, stream1_id); rtp_stream_map_[stream1_id] = linked_ptr(stream1.release()); } if (stream2) { const int stream2_id = last_transport_id_++; callback_args[1] = v8::Integer::New(isolate, stream2_id); rtp_stream_map_[stream2_id] = linked_ptr(stream2.release()); } const int udp_id = last_transport_id_++; udp_transport_map_[udp_id] = linked_ptr(udp_transport.release()); callback_args[2] = v8::Integer::New(isolate, udp_id); context()->CallFunction(create_callback_.NewHandle(isolate), 3, callback_args); create_callback_.reset(); } void CastStreamingNativeHandler::CallStartCallback(int stream_id) { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); v8::Handle event_args = v8::Array::New(isolate, 1); event_args->Set(0, v8::Integer::New(isolate, stream_id)); context()->DispatchEvent("cast.streaming.rtpStream.onStarted", event_args); } void CastStreamingNativeHandler::CallStopCallback(int stream_id) { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); v8::Handle event_args = v8::Array::New(isolate, 1); event_args->Set(0, v8::Integer::New(isolate, stream_id)); context()->DispatchEvent("cast.streaming.rtpStream.onStopped", event_args); } void CastStreamingNativeHandler::CallErrorCallback(int stream_id, const std::string& message) { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); v8::Handle event_args = v8::Array::New(isolate, 2); event_args->Set(0, v8::Integer::New(isolate, stream_id)); event_args->Set( 1, v8::String::NewFromUtf8( isolate, message.data(), v8::String::kNormalString, message.size())); context()->DispatchEvent("cast.streaming.rtpStream.onError", event_args); } void CastStreamingNativeHandler::DestroyCastRtpStream( const v8::FunctionCallbackInfo& args) { CHECK_EQ(1, args.Length()); CHECK(args[0]->IsInt32()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); if (!GetRtpStreamOrThrow(transport_id)) return; rtp_stream_map_.erase(transport_id); } void CastStreamingNativeHandler::GetSupportedParamsCastRtpStream( const v8::FunctionCallbackInfo& args) { CHECK_EQ(1, args.Length()); CHECK(args[0]->IsInt32()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); CastRtpStream* transport = GetRtpStreamOrThrow(transport_id); if (!transport) return; scoped_ptr converter(V8ValueConverter::create()); std::vector cast_params = transport->GetSupportedParams(); v8::Handle result = v8::Array::New(args.GetIsolate(), static_cast(cast_params.size())); for (size_t i = 0; i < cast_params.size(); ++i) { RtpParams params; FromCastRtpParams(cast_params[i], ¶ms); scoped_ptr params_value = params.ToValue(); result->Set( static_cast(i), converter->ToV8Value(params_value.get(), context()->v8_context())); } args.GetReturnValue().Set(result); } void CastStreamingNativeHandler::StartCastRtpStream( const v8::FunctionCallbackInfo& args) { CHECK_EQ(2, args.Length()); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsObject()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); CastRtpStream* transport = GetRtpStreamOrThrow(transport_id); if (!transport) return; scoped_ptr converter(V8ValueConverter::create()); scoped_ptr params_value( converter->FromV8Value(args[1], context()->v8_context())); if (!params_value) { args.GetIsolate()->ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertParams))); return; } scoped_ptr params = RtpParams::FromValue(*params_value); if (!params) { args.GetIsolate()->ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(args.GetIsolate(), kInvalidRtpParams))); return; } CastRtpParams cast_params; v8::Isolate* isolate = context()->v8_context()->GetIsolate(); if (!ToCastRtpParamsOrThrow(isolate, *params, &cast_params)) return; base::Closure start_callback = base::Bind(&CastStreamingNativeHandler::CallStartCallback, weak_factory_.GetWeakPtr(), transport_id); base::Closure stop_callback = base::Bind(&CastStreamingNativeHandler::CallStopCallback, weak_factory_.GetWeakPtr(), transport_id); CastRtpStream::ErrorCallback error_callback = base::Bind(&CastStreamingNativeHandler::CallErrorCallback, weak_factory_.GetWeakPtr(), transport_id); transport->Start(cast_params, start_callback, stop_callback, error_callback); } void CastStreamingNativeHandler::StopCastRtpStream( const v8::FunctionCallbackInfo& args) { CHECK_EQ(1, args.Length()); CHECK(args[0]->IsInt32()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); CastRtpStream* transport = GetRtpStreamOrThrow(transport_id); if (!transport) return; transport->Stop(); } void CastStreamingNativeHandler::DestroyCastUdpTransport( const v8::FunctionCallbackInfo& args) { CHECK_EQ(1, args.Length()); CHECK(args[0]->IsInt32()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); if (!GetUdpTransportOrThrow(transport_id)) return; udp_transport_map_.erase(transport_id); } void CastStreamingNativeHandler::SetDestinationCastUdpTransport( const v8::FunctionCallbackInfo& args) { CHECK_EQ(2, args.Length()); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsObject()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); CastUdpTransport* transport = GetUdpTransportOrThrow(transport_id); if (!transport) return; scoped_ptr converter(V8ValueConverter::create()); scoped_ptr destination_value( converter->FromV8Value(args[1], context()->v8_context())); if (!destination_value) { args.GetIsolate()->ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs))); return; } scoped_ptr destination = IPEndPoint::FromValue(*destination_value); if (!destination) { args.GetIsolate()->ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(args.GetIsolate(), kInvalidDestination))); return; } net::IPAddressNumber ip; if (!net::ParseIPLiteralToNumber(destination->address, &ip)) { args.GetIsolate()->ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(args.GetIsolate(), kInvalidDestination))); return; } transport->SetDestination(net::IPEndPoint(ip, destination->port)); } void CastStreamingNativeHandler::SetOptionsCastUdpTransport( const v8::FunctionCallbackInfo& args) { CHECK_EQ(2, args.Length()); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsObject()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); CastUdpTransport* transport = GetUdpTransportOrThrow(transport_id); if (!transport) return; scoped_ptr converter(V8ValueConverter::create()); base::Value* options_value = converter->FromV8Value(args[1], context()->v8_context()); base::DictionaryValue* options; if (!options_value || !options_value->GetAsDictionary(&options)) { delete options_value; args.GetIsolate()->ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs))); return; } transport->SetOptions(make_scoped_ptr(options)); } void CastStreamingNativeHandler::ToggleLogging( const v8::FunctionCallbackInfo& args) { CHECK_EQ(2, args.Length()); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsBoolean()); const int stream_id = args[0]->ToInt32(args.GetIsolate())->Value(); CastRtpStream* stream = GetRtpStreamOrThrow(stream_id); if (!stream) return; const bool enable = args[1]->ToBoolean(args.GetIsolate())->Value(); stream->ToggleLogging(enable); } void CastStreamingNativeHandler::GetRawEvents( const v8::FunctionCallbackInfo& args) { CHECK_EQ(3, args.Length()); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsNull() || args[1]->IsString()); CHECK(args[2]->IsFunction()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); // TODO(imcheng): Use a weak reference to ensure we don't call into an // invalid context when the callback is invoked. linked_ptr > callback( new ScopedPersistent(args[2].As())); std::string extra_data; if (!args[1]->IsNull()) { extra_data = *v8::String::Utf8Value(args[1]); } CastRtpStream* transport = GetRtpStreamOrThrow(transport_id); if (!transport) return; get_raw_events_callbacks_.insert(std::make_pair(transport_id, callback)); transport->GetRawEvents( base::Bind(&CastStreamingNativeHandler::CallGetRawEventsCallback, weak_factory_.GetWeakPtr(), transport_id), extra_data); } void CastStreamingNativeHandler::GetStats( const v8::FunctionCallbackInfo& args) { CHECK_EQ(2, args.Length()); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsFunction()); const int transport_id = args[0]->ToInt32(args.GetIsolate())->Value(); CastRtpStream* transport = GetRtpStreamOrThrow(transport_id); if (!transport) return; // TODO(imcheng): Use a weak reference to ensure we don't call into an // invalid context when the callback is invoked. linked_ptr > callback( new ScopedPersistent(args[1].As())); get_stats_callbacks_.insert(std::make_pair(transport_id, callback)); transport->GetStats( base::Bind(&CastStreamingNativeHandler::CallGetStatsCallback, weak_factory_.GetWeakPtr(), transport_id)); } void CastStreamingNativeHandler::CallGetRawEventsCallback( int transport_id, scoped_ptr raw_events) { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); RtpStreamCallbackMap::iterator it = get_raw_events_callbacks_.find(transport_id); if (it == get_raw_events_callbacks_.end()) return; v8::Handle callback_args[1]; scoped_ptr converter(V8ValueConverter::create()); callback_args[0] = converter->ToV8Value(raw_events.get(), context()->v8_context()); context()->CallFunction(it->second->NewHandle(isolate), 1, callback_args); get_raw_events_callbacks_.erase(it); } void CastStreamingNativeHandler::CallGetStatsCallback( int transport_id, scoped_ptr stats) { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); RtpStreamCallbackMap::iterator it = get_stats_callbacks_.find(transport_id); if (it == get_stats_callbacks_.end()) return; scoped_ptr converter(V8ValueConverter::create()); v8::Handle callback_args[1]; callback_args[0] = converter->ToV8Value(stats.get(), context()->v8_context()); context()->CallFunction(it->second->NewHandle(isolate), 1, callback_args); get_stats_callbacks_.erase(it); } CastRtpStream* CastStreamingNativeHandler::GetRtpStreamOrThrow( int transport_id) const { RtpStreamMap::const_iterator iter = rtp_stream_map_.find( transport_id); if (iter != rtp_stream_map_.end()) return iter->second.get(); v8::Isolate* isolate = context()->v8_context()->GetIsolate(); isolate->ThrowException(v8::Exception::RangeError(v8::String::NewFromUtf8( isolate, kRtpStreamNotFound))); return NULL; } CastUdpTransport* CastStreamingNativeHandler::GetUdpTransportOrThrow( int transport_id) const { UdpTransportMap::const_iterator iter = udp_transport_map_.find( transport_id); if (iter != udp_transport_map_.end()) return iter->second.get(); v8::Isolate* isolate = context()->v8_context()->GetIsolate(); isolate->ThrowException(v8::Exception::RangeError( v8::String::NewFromUtf8(isolate, kUdpTransportNotFound))); return NULL; } } // namespace extensions