| // Copyright (c) 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 "content/renderer/media/remote_media_stream_impl.h" |
| |
| #include <stddef.h> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "content/renderer/media/media_stream.h" |
| #include "content/renderer/media/media_stream_track.h" |
| #include "content/renderer/media/media_stream_video_track.h" |
| #include "content/renderer/media/webrtc/media_stream_remote_video_source.h" |
| #include "content/renderer/media/webrtc/peer_connection_dependency_factory.h" |
| #include "content/renderer/media/webrtc/peer_connection_remote_audio_source.h" |
| #include "content/renderer/media/webrtc/track_observer.h" |
| #include "third_party/WebKit/public/platform/WebMediaStreamSource.h" |
| #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h" |
| #include "third_party/WebKit/public/platform/WebString.h" |
| |
| namespace content { |
| namespace { |
| |
| template <typename WebRtcTrackVector, typename AdapterType> |
| void CreateAdaptersForTracks( |
| const WebRtcTrackVector& tracks, |
| std::vector<scoped_refptr<AdapterType>>* observers, |
| const scoped_refptr<base::SingleThreadTaskRunner>& main_thread) { |
| for (auto& track : tracks) |
| observers->push_back(new AdapterType(main_thread, track)); |
| } |
| |
| template<typename VectorType> |
| bool IsTrackInVector(const VectorType& v, const std::string& id) { |
| for (const auto& t : v) { |
| if (t->id() == id) |
| return true; |
| } |
| return false; |
| } |
| } // namespace |
| |
| // Base class used for mapping between webrtc and blink MediaStream tracks. |
| // An instance of a RemoteMediaStreamTrackAdapter is stored in |
| // RemoteMediaStreamImpl per remote audio and video track. |
| template<typename WebRtcMediaStreamTrackType> |
| class RemoteMediaStreamTrackAdapter |
| : public base::RefCountedThreadSafe< |
| RemoteMediaStreamTrackAdapter<WebRtcMediaStreamTrackType>> { |
| public: |
| RemoteMediaStreamTrackAdapter( |
| const scoped_refptr<base::SingleThreadTaskRunner>& main_thread, |
| WebRtcMediaStreamTrackType* webrtc_track) |
| : main_thread_(main_thread), webrtc_track_(webrtc_track), |
| id_(webrtc_track->id()) { |
| } |
| |
| const scoped_refptr<WebRtcMediaStreamTrackType>& observed_track() { |
| return webrtc_track_; |
| } |
| |
| blink::WebMediaStreamTrack* webkit_track() { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| DCHECK(!webkit_track_.isNull()); |
| return &webkit_track_; |
| } |
| |
| const std::string& id() const { return id_; } |
| |
| bool initialized() const { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| return !webkit_track_.isNull(); |
| } |
| |
| void Initialize() { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| DCHECK(!initialized()); |
| webkit_initialize_.Run(); |
| webkit_initialize_.Reset(); |
| DCHECK(initialized()); |
| } |
| |
| protected: |
| friend class base::RefCountedThreadSafe< |
| RemoteMediaStreamTrackAdapter<WebRtcMediaStreamTrackType>>; |
| |
| virtual ~RemoteMediaStreamTrackAdapter() { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| } |
| |
| void InitializeWebkitTrack(blink::WebMediaStreamSource::Type type) { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| DCHECK(webkit_track_.isNull()); |
| |
| blink::WebString webkit_track_id(blink::WebString::fromUTF8(id_)); |
| blink::WebMediaStreamSource webkit_source; |
| webkit_source.initialize(webkit_track_id, type, webkit_track_id); |
| webkit_track_.initialize(webkit_track_id, webkit_source); |
| DCHECK(!webkit_track_.isNull()); |
| } |
| |
| const scoped_refptr<base::SingleThreadTaskRunner> main_thread_; |
| // This callback will be run when Initialize() is called and then freed. |
| // The callback is used by derived classes to bind objects that need to be |
| // instantiated and initialized on the signaling thread but then moved to |
| // and used on the main thread when initializing the webkit object(s). |
| base::Callback<void()> webkit_initialize_; |
| |
| private: |
| const scoped_refptr<WebRtcMediaStreamTrackType> webrtc_track_; |
| blink::WebMediaStreamTrack webkit_track_; |
| // const copy of the webrtc track id that allows us to check it from both the |
| // main and signaling threads without incurring a synchronous thread hop. |
| const std::string id_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoteMediaStreamTrackAdapter); |
| }; |
| |
| class RemoteVideoTrackAdapter |
| : public RemoteMediaStreamTrackAdapter<webrtc::VideoTrackInterface> { |
| public: |
| // Called on the signaling thread |
| RemoteVideoTrackAdapter( |
| const scoped_refptr<base::SingleThreadTaskRunner>& main_thread, |
| webrtc::VideoTrackInterface* webrtc_track) |
| : RemoteMediaStreamTrackAdapter(main_thread, webrtc_track) { |
| std::unique_ptr<TrackObserver> observer( |
| new TrackObserver(main_thread, observed_track().get())); |
| // Here, we use base::Unretained() to avoid a circular reference. |
| webkit_initialize_ = base::Bind( |
| &RemoteVideoTrackAdapter::InitializeWebkitVideoTrack, |
| base::Unretained(this), base::Passed(&observer), |
| observed_track()->enabled()); |
| } |
| |
| protected: |
| ~RemoteVideoTrackAdapter() override { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| if (initialized()) { |
| static_cast<MediaStreamRemoteVideoSource*>( |
| webkit_track()->source().getExtraData()) |
| ->OnSourceTerminated(); |
| } |
| } |
| |
| private: |
| void InitializeWebkitVideoTrack(std::unique_ptr<TrackObserver> observer, |
| bool enabled) { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| std::unique_ptr<MediaStreamRemoteVideoSource> video_source( |
| new MediaStreamRemoteVideoSource(std::move(observer))); |
| InitializeWebkitTrack(blink::WebMediaStreamSource::TypeVideo); |
| webkit_track()->source().setExtraData(video_source.get()); |
| // Initial constraints must be provided to a MediaStreamVideoTrack. But |
| // no constraints are available initially on a remote video track. |
| blink::WebMediaConstraints constraints; |
| constraints.initialize(); |
| MediaStreamVideoTrack* media_stream_track = |
| new MediaStreamVideoTrack(video_source.release(), constraints, |
| MediaStreamVideoSource::ConstraintsCallback(), enabled); |
| webkit_track()->setTrackData(media_stream_track); |
| } |
| }; |
| |
| // RemoteAudioTrackAdapter is responsible for listening on state |
| // change notifications on a remote webrtc audio MediaStreamTracks and notify |
| // WebKit. |
| class RemoteAudioTrackAdapter |
| : public RemoteMediaStreamTrackAdapter<webrtc::AudioTrackInterface>, |
| public webrtc::ObserverInterface { |
| public: |
| RemoteAudioTrackAdapter( |
| const scoped_refptr<base::SingleThreadTaskRunner>& main_thread, |
| webrtc::AudioTrackInterface* webrtc_track); |
| |
| void Unregister(); |
| |
| protected: |
| ~RemoteAudioTrackAdapter() override; |
| |
| private: |
| void InitializeWebkitAudioTrack(); |
| |
| // webrtc::ObserverInterface implementation. |
| void OnChanged() override; |
| |
| void OnChangedOnMainThread( |
| webrtc::MediaStreamTrackInterface::TrackState state); |
| |
| #if DCHECK_IS_ON() |
| bool unregistered_; |
| #endif |
| |
| webrtc::MediaStreamTrackInterface::TrackState state_; |
| |
| DISALLOW_COPY_AND_ASSIGN(RemoteAudioTrackAdapter); |
| }; |
| |
| // Called on the signaling thread. |
| RemoteAudioTrackAdapter::RemoteAudioTrackAdapter( |
| const scoped_refptr<base::SingleThreadTaskRunner>& main_thread, |
| webrtc::AudioTrackInterface* webrtc_track) |
| : RemoteMediaStreamTrackAdapter(main_thread, webrtc_track), |
| #if DCHECK_IS_ON() |
| unregistered_(false), |
| #endif |
| state_(observed_track()->state()) { |
| // TODO(tommi): Use TrackObserver instead. |
| observed_track()->RegisterObserver(this); |
| // Here, we use base::Unretained() to avoid a circular reference. |
| webkit_initialize_ = |
| base::Bind(&RemoteAudioTrackAdapter::InitializeWebkitAudioTrack, |
| base::Unretained(this)); |
| } |
| |
| RemoteAudioTrackAdapter::~RemoteAudioTrackAdapter() { |
| #if DCHECK_IS_ON() |
| DCHECK(unregistered_); |
| #endif |
| } |
| |
| void RemoteAudioTrackAdapter::Unregister() { |
| #if DCHECK_IS_ON() |
| DCHECK(!unregistered_); |
| unregistered_ = true; |
| #endif |
| observed_track()->UnregisterObserver(this); |
| } |
| |
| void RemoteAudioTrackAdapter::InitializeWebkitAudioTrack() { |
| InitializeWebkitTrack(blink::WebMediaStreamSource::TypeAudio); |
| |
| MediaStreamAudioSource* const source = |
| new PeerConnectionRemoteAudioSource(observed_track().get()); |
| webkit_track()->source().setExtraData(source); // Takes ownership. |
| source->ConnectToTrack(*(webkit_track())); |
| } |
| |
| void RemoteAudioTrackAdapter::OnChanged() { |
| main_thread_->PostTask(FROM_HERE, |
| base::Bind(&RemoteAudioTrackAdapter::OnChangedOnMainThread, |
| this, observed_track()->state())); |
| } |
| |
| void RemoteAudioTrackAdapter::OnChangedOnMainThread( |
| webrtc::MediaStreamTrackInterface::TrackState state) { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| |
| if (state == state_ || !initialized()) |
| return; |
| |
| state_ = state; |
| |
| switch (state) { |
| case webrtc::MediaStreamTrackInterface::kLive: |
| webkit_track()->source().setReadyState( |
| blink::WebMediaStreamSource::ReadyStateLive); |
| break; |
| case webrtc::MediaStreamTrackInterface::kEnded: |
| webkit_track()->source().setReadyState( |
| blink::WebMediaStreamSource::ReadyStateEnded); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| RemoteMediaStreamImpl::Observer::Observer( |
| const base::WeakPtr<RemoteMediaStreamImpl>& media_stream, |
| const scoped_refptr<base::SingleThreadTaskRunner>& main_thread, |
| webrtc::MediaStreamInterface* webrtc_stream) |
| : media_stream_(media_stream), |
| main_thread_(main_thread), |
| webrtc_stream_(webrtc_stream) { |
| webrtc_stream_->RegisterObserver(this); |
| } |
| |
| RemoteMediaStreamImpl::Observer::~Observer() { |
| DCHECK(!webrtc_stream_.get()) << "Unregister hasn't been called"; |
| } |
| |
| void RemoteMediaStreamImpl::Observer::InitializeOnMainThread( |
| const std::string& label) { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| if (media_stream_) |
| media_stream_->InitializeOnMainThread(label); |
| } |
| |
| void RemoteMediaStreamImpl::Observer::Unregister() { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| webrtc_stream_->UnregisterObserver(this); |
| // Since we're guaranteed to not get further notifications, it's safe to |
| // release the webrtc_stream_ here. |
| webrtc_stream_ = nullptr; |
| } |
| |
| void RemoteMediaStreamImpl::Observer::OnChanged() { |
| std::unique_ptr<RemoteAudioTrackAdapters> audio( |
| new RemoteAudioTrackAdapters()); |
| std::unique_ptr<RemoteVideoTrackAdapters> video( |
| new RemoteVideoTrackAdapters()); |
| |
| CreateAdaptersForTracks( |
| webrtc_stream_->GetAudioTracks(), audio.get(), main_thread_); |
| CreateAdaptersForTracks( |
| webrtc_stream_->GetVideoTracks(), video.get(), main_thread_); |
| |
| main_thread_->PostTask(FROM_HERE, |
| base::Bind(&RemoteMediaStreamImpl::Observer::OnChangedOnMainThread, |
| this, base::Passed(&audio), base::Passed(&video))); |
| } |
| |
| void RemoteMediaStreamImpl::Observer::OnChangedOnMainThread( |
| std::unique_ptr<RemoteAudioTrackAdapters> audio_tracks, |
| std::unique_ptr<RemoteVideoTrackAdapters> video_tracks) { |
| DCHECK(main_thread_->BelongsToCurrentThread()); |
| if (media_stream_) |
| media_stream_->OnChanged(std::move(audio_tracks), std::move(video_tracks)); |
| } |
| |
| // Called on the signaling thread. |
| RemoteMediaStreamImpl::RemoteMediaStreamImpl( |
| const scoped_refptr<base::SingleThreadTaskRunner>& main_thread, |
| webrtc::MediaStreamInterface* webrtc_stream) |
| : signaling_thread_(base::ThreadTaskRunnerHandle::Get()), |
| weak_factory_(this) { |
| observer_ = new RemoteMediaStreamImpl::Observer( |
| weak_factory_.GetWeakPtr(), main_thread, webrtc_stream); |
| CreateAdaptersForTracks(webrtc_stream->GetAudioTracks(), |
| &audio_track_observers_, main_thread); |
| CreateAdaptersForTracks(webrtc_stream->GetVideoTracks(), |
| &video_track_observers_, main_thread); |
| |
| main_thread->PostTask(FROM_HERE, |
| base::Bind(&RemoteMediaStreamImpl::Observer::InitializeOnMainThread, |
| observer_, webrtc_stream->label())); |
| } |
| |
| RemoteMediaStreamImpl::~RemoteMediaStreamImpl() { |
| DCHECK(observer_->main_thread()->BelongsToCurrentThread()); |
| for (auto& track : audio_track_observers_) |
| track->Unregister(); |
| observer_->Unregister(); |
| } |
| |
| void RemoteMediaStreamImpl::InitializeOnMainThread(const std::string& label) { |
| DCHECK(observer_->main_thread()->BelongsToCurrentThread()); |
| blink::WebVector<blink::WebMediaStreamTrack> webkit_audio_tracks( |
| audio_track_observers_.size()); |
| for (size_t i = 0; i < audio_track_observers_.size(); ++i) { |
| audio_track_observers_[i]->Initialize(); |
| webkit_audio_tracks[i] = *audio_track_observers_[i]->webkit_track(); |
| } |
| |
| blink::WebVector<blink::WebMediaStreamTrack> webkit_video_tracks( |
| video_track_observers_.size()); |
| for (size_t i = 0; i < video_track_observers_.size(); ++i) { |
| video_track_observers_[i]->Initialize(); |
| webkit_video_tracks[i] = *video_track_observers_[i]->webkit_track(); |
| } |
| |
| webkit_stream_.initialize(blink::WebString::fromUTF8(label), |
| webkit_audio_tracks, webkit_video_tracks); |
| webkit_stream_.setExtraData(new MediaStream()); |
| } |
| |
| void RemoteMediaStreamImpl::OnChanged( |
| std::unique_ptr<RemoteAudioTrackAdapters> audio_tracks, |
| std::unique_ptr<RemoteVideoTrackAdapters> video_tracks) { |
| // Find removed tracks. |
| auto audio_it = audio_track_observers_.begin(); |
| while (audio_it != audio_track_observers_.end()) { |
| if (!IsTrackInVector(*audio_tracks.get(), (*audio_it)->id())) { |
| (*audio_it)->Unregister(); |
| webkit_stream_.removeTrack(*(*audio_it)->webkit_track()); |
| audio_it = audio_track_observers_.erase(audio_it); |
| } else { |
| ++audio_it; |
| } |
| } |
| |
| auto video_it = video_track_observers_.begin(); |
| while (video_it != video_track_observers_.end()) { |
| if (!IsTrackInVector(*video_tracks.get(), (*video_it)->id())) { |
| webkit_stream_.removeTrack(*(*video_it)->webkit_track()); |
| video_it = video_track_observers_.erase(video_it); |
| } else { |
| ++video_it; |
| } |
| } |
| |
| // Find added tracks. |
| for (auto& track : *audio_tracks.get()) { |
| if (!IsTrackInVector(audio_track_observers_, track->id())) { |
| track->Initialize(); |
| audio_track_observers_.push_back(track); |
| webkit_stream_.addTrack(*track->webkit_track()); |
| // Set the track to null to avoid unregistering it below now that it's |
| // been associated with a media stream. |
| track = nullptr; |
| } |
| } |
| |
| // Find added video tracks. |
| for (const auto& track : *video_tracks.get()) { |
| if (!IsTrackInVector(video_track_observers_, track->id())) { |
| track->Initialize(); |
| video_track_observers_.push_back(track); |
| webkit_stream_.addTrack(*track->webkit_track()); |
| } |
| } |
| |
| // Unregister all the audio track observers that were not used. |
| // We need to do this before destruction since the observers can't unregister |
| // from within the dtor due to a race. |
| for (auto& track : *audio_tracks.get()) { |
| if (track.get()) |
| track->Unregister(); |
| } |
| } |
| |
| } // namespace content |