diff options
author | hubbe <hubbe@chromium.org> | 2015-03-04 16:40:17 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-05 00:41:51 +0000 |
commit | 4449d976059b4d76ca1671560042f291f9a6083d (patch) | |
tree | c279bc103ad810375809ceff21ea2d5c794c6511 | |
parent | 87f25665380a8928b1cdef5cbabdb97f8e5773ff (diff) | |
download | chromium_src-4449d976059b4d76ca1671560042f291f9a6083d.zip chromium_src-4449d976059b4d76ca1671560042f291f9a6083d.tar.gz chromium_src-4449d976059b4d76ca1671560042f291f9a6083d.tar.bz2 |
Cast: Javascript bindings for cast receiver
This adds javascript bindings for the functions in cast_receiver_session, allowing
a cast_receiver to be initiated from a whitelisted javascript extension.
Also fixes a crashing bug in udp_transport.cc.
Review URL: https://codereview.chromium.org/938903003
Cr-Commit-Position: refs/heads/master@{#319175}
23 files changed, 642 insertions, 45 deletions
diff --git a/chrome/common/extensions/api/_api_features.json b/chrome/common/extensions/api/_api_features.json index 297425d..5526be9 100644 --- a/chrome/common/extensions/api/_api_features.json +++ b/chrome/common/extensions/api/_api_features.json @@ -162,6 +162,10 @@ "dependencies": ["permission:cast.streaming"], "contexts": ["blessed_extension"] }, + "cast.streaming.receiverSession": { + "dependencies": ["permission:cast.streaming"], + "contexts": ["blessed_extension"] + }, "cast.streaming.session": { "dependencies": ["permission:cast.streaming"], "contexts": ["blessed_extension"] diff --git a/chrome/common/extensions/api/cast_streaming_receiver_session.idl b/chrome/common/extensions/api/cast_streaming_receiver_session.idl new file mode 100644 index 0000000..15a7f0a7 --- /dev/null +++ b/chrome/common/extensions/api/cast_streaming_receiver_session.idl @@ -0,0 +1,70 @@ +// Copyright 2015 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. + +// The <code>chrome.cast.streaming.receiverSession</code> API creates a Cast +// receiver session and adds the resulting audio and video tracks to a +// MediaStream. +namespace cast.streaming.receiverSession { + // The UDP socket address and port. + dictionary IPEndPoint { + DOMString address; + long port; + }; + + // RTP receiver parameters. + dictionary RtpReceiverParams { + // Maximum latency in milliseconds. This parameter controls the logic + // of flow control. Implementation can adjust latency adaptively and + // tries to keep it under this threshold. A larger value allows smoother + // playback at the cost of higher latency. + long maxLatency; + + DOMString codecName; + + // Synchronization source identifier for incoming data. + long senderSsrc; + + // The SSRC used to send RTCP reports back to the sender. + long receiverSsrc; + + // RTP time units per second, defaults to 48000 for audio + // and 90000 for video. + long? rtpTimebase; + + // 32 bytes hex-encoded AES key. + DOMString? aesKey; + + // 32 bytes hex-encoded AES IV (Initialization vector) mask. + DOMString? aesIvMask; + }; + + callback ErrorCallback = void (DOMString error); + + interface Functions { + // Creates a Cast receiver session which receives data from a UDP + // socket. The receiver will decode the incoming data into an audio + // and a video track which will be added to the provided media stream. + // The |audioParams| and |videoParams| are generally provided by the + // sender through some other messaging channel. + // + // |audioParams| : Audio stream parameters. + // |videoParams| : Video stream parameters. + // |localEndpoint| : Local IP and port to bind to. + // |height| : Video height. + // |width| : Video width. + // |maxFrameRate| : Max video frame rate. + // |mediaStreamURL| : URL of MediaStream to add the audio and video to. + // |transport_options| : Optional transport settings. + [nocompile] static void createAndBind( + RtpReceiverParams audioParams, + RtpReceiverParams videoParams, + IPEndPoint localEndpoint, + long maxWidth, + long maxHeight, + double maxFrameRate, + DOMString mediaStreamURL, + ErrorCallback error_callback, + optional object transport_options); + }; +}; diff --git a/chrome/common/extensions/api/schemas.gypi b/chrome/common/extensions/api/schemas.gypi index f29dbc6..46e2711 100644 --- a/chrome/common/extensions/api/schemas.gypi +++ b/chrome/common/extensions/api/schemas.gypi @@ -120,6 +120,7 @@ ], 'webrtc_schema_files': [ + 'cast_streaming_receiver_session.idl', 'cast_streaming_rtp_stream.idl', 'cast_streaming_session.idl', 'cast_streaming_udp_transport.idl', diff --git a/chrome/renderer/extensions/DEPS b/chrome/renderer/extensions/DEPS new file mode 100644 index 0000000..75a3418 --- /dev/null +++ b/chrome/renderer/extensions/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+media/audio", +] diff --git a/chrome/renderer/extensions/cast_streaming_native_handler.cc b/chrome/renderer/extensions/cast_streaming_native_handler.cc index a10a154..1e92554 100644 --- a/chrome/renderer/extensions/cast_streaming_native_handler.cc +++ b/chrome/renderer/extensions/cast_streaming_native_handler.cc @@ -10,20 +10,29 @@ #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_receiver_session.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_receiver_session.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/child/v8_value_converter.h" +#include "content/public/renderer/media_stream_api.h" #include "extensions/renderer/script_context.h" +#include "media/audio/audio_parameters.h" #include "net/base/host_port_pair.h" +#include "third_party/WebKit/public/platform/WebMediaStream.h" #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h" +#include "third_party/WebKit/public/platform/WebURL.h" #include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h" +#include "third_party/WebKit/public/web/WebMediaStreamRegistry.h" +#include "url/gurl.h" using content::V8ValueConverter; // Extension types. +using extensions::api::cast_streaming_receiver_session::RtpReceiverParams; using extensions::api::cast_streaming_rtp_stream::CodecSpecificParams; using extensions::api::cast_streaming_rtp_stream::RtpParams; using extensions::api::cast_streaming_rtp_stream::RtpPayloadParams; @@ -32,13 +41,18 @@ 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 kInvalidAesIvMask[] = "Invalid value for AES IV mask"; +const char kInvalidAesKey[] = "Invalid value for AES key"; +const char kInvalidAudioParams[] = "Invalid audio params"; const char kInvalidDestination[] = "Invalid destination"; +const char kInvalidFPS[] = "Invalid FPS"; +const char kInvalidMediaStreamURL[] = "Invalid MediaStream URL"; 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 kInvalidLatency[] = "Invalid value for max_latency. (0-1000)"; +const char kInvalidRtpTimebase[] = "Invalid rtp_timebase. (1000-1000000)"; const char kInvalidStreamArgs[] = "Invalid stream arguments"; +const char kRtpStreamNotFound[] = "The RTP stream cannot be found"; +const char kUdpTransportNotFound[] = "The UDP transport cannot be found"; const char kUnableToConvertArgs[] = "Unable to convert arguments"; const char kUnableToConvertParams[] = "Unable to convert params"; @@ -199,6 +213,9 @@ CastStreamingNativeHandler::CastStreamingNativeHandler(ScriptContext* context) RouteFunction("GetStats", base::Bind(&CastStreamingNativeHandler::GetStats, base::Unretained(this))); + RouteFunction("StartCastRtpReceiver", + base::Bind(&CastStreamingNativeHandler::StartCastRtpReceiver, + base::Unretained(this))); } CastStreamingNativeHandler::~CastStreamingNativeHandler() { @@ -440,28 +457,17 @@ void CastStreamingNativeHandler::SetDestinationCastUdpTransport( if (!transport) return; - scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); - scoped_ptr<base::Value> destination_value( - converter->FromV8Value(args[1], context()->v8_context())); - if (!destination_value) { - args.GetIsolate()->ThrowException(v8::Exception::TypeError( - v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs))); + net::IPEndPoint dest; + if (!IPEndPointFromArg(args.GetIsolate(), + args[1], + &dest)) { return; } - scoped_ptr<IPEndPoint> 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)); + transport->SetDestination( + dest, + base::Bind(&CastStreamingNativeHandler::CallErrorCallback, + weak_factory_.GetWeakPtr(), + transport_id)); } void CastStreamingNativeHandler::SetOptionsCastUdpTransport( @@ -616,4 +622,239 @@ CastUdpTransport* CastStreamingNativeHandler::GetUdpTransportOrThrow( return NULL; } +bool CastStreamingNativeHandler::FrameReceiverConfigFromArg( + v8::Isolate* isolate, + const v8::Handle<v8::Value>& arg, + media::cast::FrameReceiverConfig* config) { + + scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); + scoped_ptr<base::Value> params_value( + converter->FromV8Value(arg, context()->v8_context())); + if (!params_value) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, kUnableToConvertParams))); + return false; + } + scoped_ptr<RtpReceiverParams> params = + RtpReceiverParams::FromValue(*params_value); + if (!params) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, kInvalidRtpParams))); + return false; + } + + config->receiver_ssrc = params->receiver_ssrc; + config->sender_ssrc = params->sender_ssrc; + config->rtp_max_delay_ms = params->max_latency; + if (config->rtp_max_delay_ms < 0 || config->rtp_max_delay_ms > 1000) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, kInvalidLatency))); + return false; + } + config->channels = 2; + if (params->codec_name == "OPUS") { + config->codec = media::cast::CODEC_AUDIO_OPUS; + config->rtp_timebase = 48000; + config->rtp_payload_type = 127; + } else if (params->codec_name == "PCM16") { + config->codec = media::cast::CODEC_AUDIO_PCM16; + config->rtp_timebase = 48000; + config->rtp_payload_type =127; + } else if (params->codec_name == "AAC") { + config->codec = media::cast::CODEC_AUDIO_AAC; + config->rtp_timebase = 48000; + config->rtp_payload_type = 127; + } else if (params->codec_name == "VP8") { + config->codec = media::cast::CODEC_VIDEO_VP8; + config->rtp_timebase = 90000; + config->rtp_payload_type = 96; + } else if (params->codec_name == "H264") { + config->codec = media::cast::CODEC_VIDEO_H264; + config->rtp_timebase = 90000; + config->rtp_payload_type = 96; + } + if (params->rtp_timebase) { + config->rtp_timebase = *params->rtp_timebase; + if (config->rtp_timebase < 1000 || config->rtp_timebase > 1000000) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, kInvalidRtpTimebase))); + return false; + } + } + if (params->aes_key && + !HexDecode(*params->aes_key, &config->aes_key)) { + isolate->ThrowException(v8::Exception::Error( + v8::String::NewFromUtf8(isolate, kInvalidAesKey))); + return false; + } + if (params->aes_iv_mask && + !HexDecode(*params->aes_iv_mask, &config->aes_iv_mask)) { + isolate->ThrowException(v8::Exception::Error( + v8::String::NewFromUtf8(isolate, kInvalidAesIvMask))); + return false; + } + return true; +} + +bool CastStreamingNativeHandler::IPEndPointFromArg( + v8::Isolate* isolate, + const v8::Handle<v8::Value>& arg, + net::IPEndPoint* ip_endpoint) { + scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); + scoped_ptr<base::Value> destination_value( + converter->FromV8Value(arg, context()->v8_context())); + if (!destination_value) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, kInvalidAesIvMask))); + return false; + } + scoped_ptr<IPEndPoint> destination = + IPEndPoint::FromValue(*destination_value); + if (!destination) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, kInvalidDestination))); + return false; + } + net::IPAddressNumber ip; + if (!net::ParseIPLiteralToNumber(destination->address, &ip)) { + isolate->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(isolate, kInvalidDestination))); + return false; + } + *ip_endpoint = net::IPEndPoint(ip, destination->port); + return true; +} + +void CastStreamingNativeHandler::StartCastRtpReceiver( + const v8::FunctionCallbackInfo<v8::Value>& args) { + if (args.Length() < 8 || args.Length() > 9 || + !args[0]->IsObject() || + !args[1]->IsObject() || + !args[2]->IsObject() || + !args[3]->IsInt32() || + !args[4]->IsInt32() || + !args[5]->IsNumber() || + !args[6]->IsString()) { + args.GetIsolate()->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs))); + return; + } + + v8::Isolate* isolate = context()->v8_context()->GetIsolate(); + + scoped_refptr<CastReceiverSession> session( + new CastReceiverSession()); + media::cast::FrameReceiverConfig audio_config; + media::cast::FrameReceiverConfig video_config; + net::IPEndPoint local_endpoint; + net::IPEndPoint remote_endpoint; + + if (!FrameReceiverConfigFromArg(isolate, args[0], &audio_config) || + !FrameReceiverConfigFromArg(isolate, args[1], &video_config) || + !IPEndPointFromArg(isolate, args[2], &local_endpoint)) { + return; + } + + const std::string url = *v8::String::Utf8Value(args[7]); + blink::WebMediaStream stream = + blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor(GURL(url)); + + if (stream.isNull()) { + args.GetIsolate()->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(args.GetIsolate(), kInvalidMediaStreamURL))); + return; + } + + const int max_width = args[3]->ToInt32(args.GetIsolate())->Value(); + const int max_height = args[4]->ToInt32(args.GetIsolate())->Value(); + const double fps = args[5]->NumberValue(); + + if (fps <= 1) { + args.GetIsolate()->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(args.GetIsolate(), kInvalidFPS))); + return; + } + + media::VideoCaptureFormat capture_format( + gfx::Size(max_width, max_height), + fps, + media::PIXEL_FORMAT_I420); + + video_config.target_frame_rate = fps; + audio_config.target_frame_rate = 100; + + media::AudioParameters params( + media::AudioParameters::AUDIO_PCM_LINEAR, + media::CHANNEL_LAYOUT_STEREO, + audio_config.rtp_timebase, // sampling rate + 16, + audio_config.rtp_timebase / audio_config.target_frame_rate); + + if (!params.IsValid()) { + args.GetIsolate()->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(args.GetIsolate(), kInvalidAudioParams))); + return; + } + + base::DictionaryValue* options = NULL; + if (args.Length() >= 10) { + scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create()); + base::Value* options_value = + converter->FromV8Value(args[8], context()->v8_context()); + if (!options_value->IsType(base::Value::TYPE_NULL)) { + if (!options_value || !options_value->GetAsDictionary(&options)) { + delete options_value; + args.GetIsolate()->ThrowException(v8::Exception::TypeError( + v8::String::NewFromUtf8(args.GetIsolate(), kUnableToConvertArgs))); + return; + } + } + } + + if (!options) { + options = new base::DictionaryValue(); + } + + v8::CopyablePersistentTraits<v8::Function>::CopyablePersistent error_callback; + error_callback.Reset(args.GetIsolate(), + v8::Handle<v8::Function>(args[7].As<v8::Function>())); + + session->Start( + audio_config, + video_config, + local_endpoint, + remote_endpoint, + make_scoped_ptr(options), + capture_format, + base::Bind(&CastStreamingNativeHandler::AddTracksToMediaStream, + weak_factory_.GetWeakPtr(), + url, + params), + base::Bind(&CastStreamingNativeHandler::CallReceiverErrorCallback, + weak_factory_.GetWeakPtr(), + error_callback)); +} + +void CastStreamingNativeHandler::CallReceiverErrorCallback( + v8::CopyablePersistentTraits<v8::Function>::CopyablePersistent function, + const std::string& error_message) { + v8::Isolate* isolate = context()->v8_context()->GetIsolate(); + v8::Handle<v8::Value> arg = v8::String::NewFromUtf8(isolate, + error_message.data(), + v8::String::kNormalString, + error_message.size()); + context()->CallFunction( + v8::Local<v8::Function>::New(isolate, function), 1, &arg); +} + + +void CastStreamingNativeHandler::AddTracksToMediaStream( + const std::string& url, + const media::AudioParameters& params, + scoped_refptr<media::AudioCapturerSource> audio, + scoped_ptr<media::VideoCapturerSource> video) { + content::AddAudioTrackToMediaStream(audio, params, true, true, url); + content::AddVideoTrackToMediaStream(video.Pass(), true, true, url); +} + } // namespace extensions diff --git a/chrome/renderer/extensions/cast_streaming_native_handler.h b/chrome/renderer/extensions/cast_streaming_native_handler.h index 225ade0..64a56e4e 100644 --- a/chrome/renderer/extensions/cast_streaming_native_handler.h +++ b/chrome/renderer/extensions/cast_streaming_native_handler.h @@ -21,6 +21,23 @@ class BinaryValue; class DictionaryValue; } +namespace blink { +class WebMediaStream; +} + +namespace net { +class IPEndPoint; +} + +namespace media { +class AudioCapturerSource; +class AudioParameters; +class VideoCapturerSource; +namespace cast { +struct FrameReceiverConfig; +} +} + namespace extensions { // Native code that handle chrome.webrtc custom bindings. @@ -52,6 +69,8 @@ class CastStreamingNativeHandler : public ObjectBackedNativeHandler { const v8::FunctionCallbackInfo<v8::Value>& args); void StopCastUdpTransport( const v8::FunctionCallbackInfo<v8::Value>& args); + void StartCastRtpReceiver( + const v8::FunctionCallbackInfo<v8::Value>& args); void ToggleLogging(const v8::FunctionCallbackInfo<v8::Value>& args); void GetRawEvents(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -67,6 +86,20 @@ class CastStreamingNativeHandler : public ObjectBackedNativeHandler { void CallStopCallback(int stream_id); void CallErrorCallback(int stream_id, const std::string& message); + // Callback called after a cast receiver has been started. Adds the + // output audio/video streams to the MediaStream specified by |url|. + void AddTracksToMediaStream( + const std::string& url, + const media::AudioParameters& params, + scoped_refptr<media::AudioCapturerSource> audio, + scoped_ptr<media::VideoCapturerSource> video); + + // |function| is a javascript function that will take |error_message| as + // an argument. Called when something goes wrong in a cast receiver. + void CallReceiverErrorCallback( + v8::CopyablePersistentTraits<v8::Function>::CopyablePersistent function, + const std::string& error_message); + void CallGetRawEventsCallback(int transport_id, scoped_ptr<base::BinaryValue> raw_events); void CallGetStatsCallback(int transport_id, @@ -77,6 +110,19 @@ class CastStreamingNativeHandler : public ObjectBackedNativeHandler { CastRtpStream* GetRtpStreamOrThrow(int stream_id) const; CastUdpTransport* GetUdpTransportOrThrow(int transport_id) const; + // Fills out a media::cast::FrameReceiverConfig from the v8 + // equivialent. (cast.streaming.receiverSession.RtpReceiverParams) + // Returns true if everything was ok, raises a v8 exception and + // returns false if anything went wrong. + bool FrameReceiverConfigFromArg( + v8::Isolate* isolate, + const v8::Handle<v8::Value>& arg, + media::cast::FrameReceiverConfig* config); + + bool IPEndPointFromArg(v8::Isolate* isolate, + const v8::Handle<v8::Value>& arg, + net::IPEndPoint* ip_endpoint); + int last_transport_id_; typedef std::map<int, linked_ptr<CastRtpStream> > RtpStreamMap; diff --git a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc index 88df7cb..77f2f17 100644 --- a/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc +++ b/chrome/renderer/extensions/chrome_extensions_dispatcher_delegate.cc @@ -214,6 +214,9 @@ void ChromeExtensionsDispatcherDelegate::PopulateSourceMap( source_map->RegisterSource( "cast.streaming.udpTransport", IDR_CAST_STREAMING_UDP_TRANSPORT_CUSTOM_BINDINGS_JS); + source_map->RegisterSource( + "cast.streaming.receiverSession", + IDR_CAST_STREAMING_RECEIVER_SESSION_CUSTOM_BINDINGS_JS); #endif source_map->RegisterSource("webstore", IDR_WEBSTORE_CUSTOM_BINDINGS_JS); diff --git a/chrome/renderer/media/cast_receiver_session.cc b/chrome/renderer/media/cast_receiver_session.cc index 6870f23..d1ba653 100644 --- a/chrome/renderer/media/cast_receiver_session.cc +++ b/chrome/renderer/media/cast_receiver_session.cc @@ -8,6 +8,7 @@ #include "chrome/renderer/media/cast_receiver_audio_valve.h" #include "content/public/renderer/render_thread.h" #include "media/base/audio_capturer_source.h" +#include "media/base/bind_to_current_loop.h" #include "media/base/video_capturer_source.h" #include "third_party/WebKit/public/platform/WebMediaStream.h" #include "third_party/WebKit/public/platform/WebMediaStreamSource.h" @@ -71,7 +72,8 @@ void CastReceiverSession::Start( const net::IPEndPoint& remote_endpoint, scoped_ptr<base::DictionaryValue> options, const media::VideoCaptureFormat& capture_format, - const StartCB& start_callback) { + const StartCB& start_callback, + const CastReceiverSessionDelegate::ErrorCallback& error_callback) { audio_config_ = audio_config; video_config_ = video_config; format_ = capture_format; @@ -84,7 +86,8 @@ void CastReceiverSession::Start( local_endpoint, remote_endpoint, base::Passed(&options), - format_)); + format_, + media::BindToCurrentLoop(error_callback))); scoped_refptr<media::AudioCapturerSource> audio( new CastReceiverSession::AudioCapturerSource(this)); scoped_ptr<media::VideoCapturerSource> video( diff --git a/chrome/renderer/media/cast_receiver_session.h b/chrome/renderer/media/cast_receiver_session.h index 0827879..d292522 100644 --- a/chrome/renderer/media/cast_receiver_session.h +++ b/chrome/renderer/media/cast_receiver_session.h @@ -49,7 +49,8 @@ class CastReceiverSession : public base::RefCounted<CastReceiverSession> { const net::IPEndPoint& remote_endpoint, scoped_ptr<base::DictionaryValue> options, const media::VideoCaptureFormat& capture_format, - const StartCB& start_callback); + const StartCB& start_callback, + const CastReceiverSessionDelegate::ErrorCallback& error_callback); private: class VideoCapturerSource; diff --git a/chrome/renderer/media/cast_receiver_session_delegate.cc b/chrome/renderer/media/cast_receiver_session_delegate.cc index 5fc5844..3509989 100644 --- a/chrome/renderer/media/cast_receiver_session_delegate.cc +++ b/chrome/renderer/media/cast_receiver_session_delegate.cc @@ -24,12 +24,14 @@ void CastReceiverSessionDelegate::Start( const net::IPEndPoint& local_endpoint, const net::IPEndPoint& remote_endpoint, scoped_ptr<base::DictionaryValue> options, - const media::VideoCaptureFormat& format) { + const media::VideoCaptureFormat& format, + const ErrorCallback& error_callback) { format_ = format; DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); CastSessionDelegateBase::StartUDP(local_endpoint, remote_endpoint, - options.Pass()); + options.Pass(), + error_callback); cast_receiver_ = media::cast::CastReceiver::Create(cast_environment_, audio_config, video_config, diff --git a/chrome/renderer/media/cast_receiver_session_delegate.h b/chrome/renderer/media/cast_receiver_session_delegate.h index 804d944..98de8fd 100644 --- a/chrome/renderer/media/cast_receiver_session_delegate.h +++ b/chrome/renderer/media/cast_receiver_session_delegate.h @@ -13,6 +13,8 @@ class CastReceiverSessionDelegate : public CastSessionDelegateBase { public: + typedef base::Callback<void(const std::string&)> ErrorCallback; + CastReceiverSessionDelegate(); ~CastReceiverSessionDelegate() override; @@ -26,7 +28,8 @@ class CastReceiverSessionDelegate : public CastSessionDelegateBase { const net::IPEndPoint& local_endpoint, const net::IPEndPoint& remote_endpoint, scoped_ptr<base::DictionaryValue> options, - const media::VideoCaptureFormat& format); + const media::VideoCaptureFormat& format, + const ErrorCallback& error_callback); void StartAudio(scoped_refptr<CastReceiverAudioValve> audio_valve); diff --git a/chrome/renderer/media/cast_session.cc b/chrome/renderer/media/cast_session.cc index 0990d3a..0983a2b 100644 --- a/chrome/renderer/media/cast_session.cc +++ b/chrome/renderer/media/cast_session.cc @@ -85,7 +85,8 @@ void CastSession::StartVideo(const media::cast::VideoSenderConfig& config, } void CastSession::StartUDP(const net::IPEndPoint& remote_endpoint, - scoped_ptr<base::DictionaryValue> options) { + scoped_ptr<base::DictionaryValue> options, + const ErrorCallback& error_callback) { io_message_loop_proxy_->PostTask( FROM_HERE, base::Bind( @@ -93,7 +94,8 @@ void CastSession::StartUDP(const net::IPEndPoint& remote_endpoint, base::Unretained(delegate_.get()), net::IPEndPoint(), remote_endpoint, - base::Passed(&options))); + base::Passed(&options), + media::BindToCurrentLoop(error_callback))); } void CastSession::ToggleLogging(bool is_audio, bool enable) { diff --git a/chrome/renderer/media/cast_session.h b/chrome/renderer/media/cast_session.h index 12e602f..afffb35 100644 --- a/chrome/renderer/media/cast_session.h +++ b/chrome/renderer/media/cast_session.h @@ -71,7 +71,8 @@ class CastSession : public base::RefCounted<CastSession> { // udp transport. // Must be called before initialization of audio or video. void StartUDP(const net::IPEndPoint& remote_endpoint, - scoped_ptr<base::DictionaryValue> options); + scoped_ptr<base::DictionaryValue> options, + const ErrorCallback& error_callback); // Creates or destroys event subscriber for the audio or video stream. // |is_audio|: true if the event subscriber is for audio. Video otherwise. diff --git a/chrome/renderer/media/cast_session_delegate.cc b/chrome/renderer/media/cast_session_delegate.cc index 6a0da85..2b07386 100644 --- a/chrome/renderer/media/cast_session_delegate.cc +++ b/chrome/renderer/media/cast_session_delegate.cc @@ -45,7 +45,8 @@ CastSessionDelegateBase::~CastSessionDelegateBase() { void CastSessionDelegateBase::StartUDP( const net::IPEndPoint& local_endpoint, const net::IPEndPoint& remote_endpoint, - scoped_ptr<base::DictionaryValue> options) { + scoped_ptr<base::DictionaryValue> options, + const ErrorCallback& error_callback) { DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); // CastSender uses the renderer's IO thread as the main thread. This reduces @@ -66,15 +67,30 @@ void CastSessionDelegateBase::StartUDP( base::Bind(&CastSessionDelegateBase::ReceivePacket, base::Unretained(this)), base::Bind(&CastSessionDelegateBase::StatusNotificationCB, - base::Unretained(this)), + base::Unretained(this), error_callback), base::Bind(&CastSessionDelegateBase::LogRawEvents, base::Unretained(this)))); } void CastSessionDelegateBase::StatusNotificationCB( - media::cast::CastTransportStatus unused_status) { + const ErrorCallback& error_callback, + media::cast::CastTransportStatus status) { DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); - // TODO(hubbe): Call javascript UDPTransport error function. + std::string error_message; + + switch (status) { + case media::cast::TRANSPORT_AUDIO_UNINITIALIZED: + case media::cast::TRANSPORT_VIDEO_UNINITIALIZED: + case media::cast::TRANSPORT_AUDIO_INITIALIZED: + case media::cast::TRANSPORT_VIDEO_INITIALIZED: + return; // Not errors, do nothing. + case media::cast::TRANSPORT_INVALID_CRYPTO_CONFIG: + error_callback.Run("Invalid encrypt/decrypt configuration."); + break; + case media::cast::TRANSPORT_SOCKET_ERROR: + error_callback.Run("Socket error."); + break; + } } CastSessionDelegate::CastSessionDelegate() @@ -132,11 +148,13 @@ void CastSessionDelegate::StartVideo( void CastSessionDelegate::StartUDP( const net::IPEndPoint& local_endpoint, const net::IPEndPoint& remote_endpoint, - scoped_ptr<base::DictionaryValue> options) { + scoped_ptr<base::DictionaryValue> options, + const ErrorCallback& error_callback) { DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); CastSessionDelegateBase::StartUDP(local_endpoint, remote_endpoint, - options.Pass()); + options.Pass(), + error_callback); event_subscribers_.reset( new media::cast::RawEventSubscriberBundle(cast_environment_)); diff --git a/chrome/renderer/media/cast_session_delegate.h b/chrome/renderer/media/cast_session_delegate.h index 7528147..a2e688c 100644 --- a/chrome/renderer/media/cast_session_delegate.h +++ b/chrome/renderer/media/cast_session_delegate.h @@ -43,6 +43,8 @@ class CastTransportSender; // CastReceiverSessionDelegate. class CastSessionDelegateBase { public: + typedef base::Callback<void(const std::string&)> ErrorCallback; + CastSessionDelegateBase(); virtual ~CastSessionDelegateBase(); @@ -51,10 +53,12 @@ class CastSessionDelegateBase { // Must be called before initialization of audio or video. void StartUDP(const net::IPEndPoint& local_endpoint, const net::IPEndPoint& remote_endpoint, - scoped_ptr<base::DictionaryValue> options); + scoped_ptr<base::DictionaryValue> options, + const ErrorCallback& error_callback); protected: void StatusNotificationCB( + const ErrorCallback& error_callback, media::cast::CastTransportStatus status); virtual void ReceivePacket(scoped_ptr<media::cast::Packet> packet) = 0; @@ -85,14 +89,14 @@ class CastSessionDelegate : public CastSessionDelegateBase { media::cast::VideoFrameInput>&)> VideoFrameInputAvailableCallback; typedef base::Callback<void(scoped_ptr<base::BinaryValue>)> EventLogsCallback; typedef base::Callback<void(scoped_ptr<base::DictionaryValue>)> StatsCallback; - typedef base::Callback<void(const std::string&)> ErrorCallback; CastSessionDelegate(); ~CastSessionDelegate() override; void StartUDP(const net::IPEndPoint& local_endpoint, const net::IPEndPoint& remote_endpoint, - scoped_ptr<base::DictionaryValue> options); + scoped_ptr<base::DictionaryValue> options, + const ErrorCallback& error_callback); // After calling StartAudio() or StartVideo() encoding of that media will // begin as soon as data is delivered to its sink, if the second method is diff --git a/chrome/renderer/media/cast_udp_transport.cc b/chrome/renderer/media/cast_udp_transport.cc index 0cecbca..3861873 100644 --- a/chrome/renderer/media/cast_udp_transport.cc +++ b/chrome/renderer/media/cast_udp_transport.cc @@ -17,12 +17,15 @@ CastUdpTransport::CastUdpTransport( CastUdpTransport::~CastUdpTransport() { } -void CastUdpTransport::SetDestination(const net::IPEndPoint& remote_address) { +void CastUdpTransport::SetDestination( + const net::IPEndPoint& remote_address, + const CastSessionDelegate::ErrorCallback& error_callback) { DVLOG(1) << "CastUdpTransport::SetDestination = " << remote_address.ToString(); remote_address_ = remote_address; cast_session_->StartUDP(remote_address, - make_scoped_ptr(options_->DeepCopy())); + make_scoped_ptr(options_->DeepCopy()), + error_callback); } void CastUdpTransport::SetOptions(scoped_ptr<base::DictionaryValue> options) { diff --git a/chrome/renderer/media/cast_udp_transport.h b/chrome/renderer/media/cast_udp_transport.h index ee10c24..d105c5f 100644 --- a/chrome/renderer/media/cast_udp_transport.h +++ b/chrome/renderer/media/cast_udp_transport.h @@ -8,6 +8,7 @@ #include "base/basictypes.h" #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" +#include "chrome/renderer/media/cast_session_delegate.h" #include "net/base/ip_endpoint.h" namespace base { @@ -25,7 +26,8 @@ class CastUdpTransport { virtual ~CastUdpTransport(); // Specify the remote IP address and port. - void SetDestination(const net::IPEndPoint& remote_address); + void SetDestination(const net::IPEndPoint& remote_address, + const CastSessionDelegate::ErrorCallback& error_callback); // Set options. void SetOptions(scoped_ptr<base::DictionaryValue> options); diff --git a/chrome/renderer/resources/extensions/cast_streaming_receiver_session_custom_bindings.js b/chrome/renderer/resources/extensions/cast_streaming_receiver_session_custom_bindings.js new file mode 100644 index 0000000..8751071 --- /dev/null +++ b/chrome/renderer/resources/extensions/cast_streaming_receiver_session_custom_bindings.js @@ -0,0 +1,20 @@ +// 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. + +// Custom binding for the Cast Streaming Session API. + +var binding = require('binding').Binding.create( + 'cast.streaming.receiverSession'); +var natives = requireNative('cast_streaming_natives'); + +binding.registerCustomHook(function(bindingsAPI, extensionId) { + var apiFunctions = bindingsAPI.apiFunctions; + apiFunctions.setHandleRequest('createAndBind', + function(ap, vp, local, weidgth, height, fr, url, cb, op) { + natives.StartCastRtpReceiver( + ap, vp, local, weidgth, height, fr, url, cb, op); + }); +}); + +exports.binding = binding.generate(); diff --git a/chrome/renderer/resources/renderer_resources.grd b/chrome/renderer/resources/renderer_resources.grd index 150ec9e..53b221d 100644 --- a/chrome/renderer/resources/renderer_resources.grd +++ b/chrome/renderer/resources/renderer_resources.grd @@ -39,6 +39,7 @@ <include name="IDR_CAST_STREAMING_RTP_STREAM_CUSTOM_BINDINGS_JS" file="extensions\cast_streaming_rtp_stream_custom_bindings.js" type="BINDATA" /> <include name="IDR_CAST_STREAMING_SESSION_CUSTOM_BINDINGS_JS" file="extensions\cast_streaming_session_custom_bindings.js" type="BINDATA" /> <include name="IDR_CAST_STREAMING_UDP_TRANSPORT_CUSTOM_BINDINGS_JS" file="extensions\cast_streaming_udp_transport_custom_bindings.js" type="BINDATA" /> + <include name="IDR_CAST_STREAMING_RECEIVER_SESSION_CUSTOM_BINDINGS_JS" file="extensions\cast_streaming_receiver_session_custom_bindings.js" type="BINDATA" /> <include name="IDR_CHROME_DIRECT_SETTING_JS" file="extensions\chrome_direct_setting.js" type="BINDATA" /> <include name="IDR_CHROME_SETTING_JS" file="extensions\chrome_setting.js" type="BINDATA" /> diff --git a/content/content_renderer.gypi b/content/content_renderer.gypi index 7c9acb0..6a0de9e 100644 --- a/content/content_renderer.gypi +++ b/content/content_renderer.gypi @@ -588,6 +588,8 @@ 'renderer/render_widget_fullscreen_pepper.h', ], 'public_renderer_webrtc_sources': [ + 'public/renderer/media_stream_api.cc', + 'public/renderer/media_stream_api.h', 'public/renderer/media_stream_audio_sink.cc', 'public/renderer/media_stream_audio_sink.h', 'public/renderer/media_stream_sink.h', diff --git a/content/public/renderer/BUILD.gn b/content/public/renderer/BUILD.gn index 5de959b..0ab2d33 100644 --- a/content/public/renderer/BUILD.gn +++ b/content/public/renderer/BUILD.gn @@ -49,6 +49,7 @@ source_set("renderer_sources") { rebase_path(content_renderer_gypi_values.public_renderer_webrtc_sources, ".", "//content") + deps += [ "//third_party/webrtc" ] } if (enable_plugins) { diff --git a/content/public/renderer/media_stream_api.cc b/content/public/renderer/media_stream_api.cc new file mode 100644 index 0000000..f8fd76d --- /dev/null +++ b/content/public/renderer/media_stream_api.cc @@ -0,0 +1,125 @@ +// Copyright 2015 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 "content/public/renderer/media_stream_api.h" + +#include "base/base64.h" +#include "base/callback.h" +#include "base/rand_util.h" +#include "base/strings/utf_string_conversions.h" +#include "content/renderer/media/media_stream_audio_source.h" +#include "content/renderer/media/media_stream_video_capturer_source.h" +#include "content/renderer/media/media_stream_video_track.h" +#include "media/base/audio_capturer_source.h" +#include "media/base/video_capturer_source.h" +#include "third_party/WebKit/public/platform/WebMediaDeviceInfo.h" +#include "third_party/WebKit/public/platform/WebMediaStream.h" +#include "third_party/WebKit/public/platform/WebMediaStreamSource.h" +#include "third_party/WebKit/public/platform/WebURL.h" +#include "third_party/WebKit/public/web/WebMediaStreamRegistry.h" +#include "url/gurl.h" + +namespace content { + +namespace { + +blink::WebString MakeTrackId() { + std::string track_id; + base::Base64Encode(base::RandBytesAsString(64), &track_id); + return base::UTF8ToUTF16(track_id); +} + +} // namespace + +bool AddVideoTrackToMediaStream( + scoped_ptr<media::VideoCapturerSource> source, + bool is_remote, + bool is_readonly, + const std::string& media_stream_url) { + blink::WebMediaStream stream = + blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor( + GURL(media_stream_url)); + + if (stream.isNull()) { + LOG(ERROR) << "Stream not found"; + return false; + } + blink::WebString track_id = MakeTrackId(); + blink::WebMediaStreamSource webkit_source; + scoped_ptr<MediaStreamVideoSource> media_stream_source( + new MediaStreamVideoCapturerSource( + MediaStreamSource::SourceStoppedCallback(), + source.Pass())); + webkit_source.initialize( + track_id, + blink::WebMediaStreamSource::TypeVideo, + track_id, + is_remote, + is_readonly); + webkit_source.setExtraData(media_stream_source.get()); + + blink::WebMediaConstraints constraints; + constraints.initialize(); + stream.addTrack(MediaStreamVideoTrack::CreateVideoTrack( + media_stream_source.release(), + constraints, + MediaStreamVideoSource::ConstraintsCallback(), + true)); + return true; +} + +bool AddAudioTrackToMediaStream( + scoped_refptr<media::AudioCapturerSource> source, + const media::AudioParameters& params, + bool is_remote, + bool is_readonly, + const std::string& media_stream_url) { + DCHECK(params.IsValid()) << params.AsHumanReadableString(); + blink::WebMediaStream stream = + blink::WebMediaStreamRegistry::lookupMediaStreamDescriptor( + GURL(media_stream_url)); + + if (stream.isNull()) { + LOG(ERROR) << "Stream not found"; + return false; + } + blink::WebMediaStreamSource webkit_source; + blink::WebString track_id = MakeTrackId(); + webkit_source.initialize( + track_id, + blink::WebMediaStreamSource::TypeAudio, + track_id, + is_remote, + is_readonly); + + MediaStreamAudioSource* audio_source( + new MediaStreamAudioSource( + -1, + StreamDeviceInfo(), + MediaStreamSource::SourceStoppedCallback(), + RenderThreadImpl::current()->GetPeerConnectionDependencyFactory())); + + blink::WebMediaConstraints constraints; + constraints.initialize(); + scoped_refptr<WebRtcAudioCapturer> capturer( + WebRtcAudioCapturer::CreateCapturer( + -1, + StreamDeviceInfo(), + constraints, + nullptr, + audio_source)); + capturer->SetCapturerSource(source, params); + audio_source->SetAudioCapturer(capturer); + webkit_source.setExtraData(audio_source); + + blink::WebMediaStreamTrack web_media_audio_track; + web_media_audio_track.initialize(webkit_source); + RenderThreadImpl::current()->GetPeerConnectionDependencyFactory()-> + CreateLocalAudioTrack(web_media_audio_track); + + stream.addTrack(web_media_audio_track); + return true; +} + +} // namespace content diff --git a/content/public/renderer/media_stream_api.h b/content/public/renderer/media_stream_api.h new file mode 100644 index 0000000..71ce675 --- /dev/null +++ b/content/public/renderer/media_stream_api.h @@ -0,0 +1,41 @@ +// Copyright 2015 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. + +#ifndef CONTENT_PUBLIC_RENDERER_MEDIA_STREAM_API_H_ +#define CONTENT_PUBLIC_RENDERER_MEDIA_STREAM_API_H_ + +#include "content/common/content_export.h" +#include "media/base/audio_capturer_source.h" +#include "media/base/video_capturer_source.h" + +namespace blink { +class WebMediaStreamSource; +} + +namespace Media { +class AudioParameters; +} + +namespace content { + +// These two methods will initialize a WebMediaStreamSource object to take +// data from the provided audio or video capturer source. +// |is_remote| should be true if the source of the data is not a local device. +// |is_readonly| should be true if the format of the data cannot be changed by +// MediaTrackConstraints. +CONTENT_EXPORT bool AddVideoTrackToMediaStream( + scoped_ptr<media::VideoCapturerSource> source, + bool is_remote, + bool is_readonly, + const std::string& media_stream_url); +CONTENT_EXPORT bool AddAudioTrackToMediaStream( + scoped_refptr<media::AudioCapturerSource> source, + const media::AudioParameters& params, + bool is_remote, + bool is_readonly, + const std::string& media_stream_url); + +} // namespace content + +#endif // CONTENT_PUBLIC_RENDERER_MEDIA_STREAM_API_H_ |