1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "AudioSinkWrapper.h"
8 #include "AudioSink.h"
9 #include "VideoUtils.h"
10 #include "nsPrintfCString.h"
11 
12 namespace mozilla {
13 
14 using media::TimeUnit;
15 
16 AudioSinkWrapper::~AudioSinkWrapper() = default;
17 
Shutdown()18 void AudioSinkWrapper::Shutdown() {
19   AssertOwnerThread();
20   MOZ_ASSERT(!mIsStarted, "Must be called after playback stopped.");
21   mCreator = nullptr;
22 }
23 
OnEnded(TrackType aType)24 RefPtr<MediaSink::EndedPromise> AudioSinkWrapper::OnEnded(TrackType aType) {
25   AssertOwnerThread();
26   MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
27   if (aType == TrackInfo::kAudioTrack) {
28     return mEndedPromise;
29   }
30   return nullptr;
31 }
32 
GetEndTime(TrackType aType) const33 TimeUnit AudioSinkWrapper::GetEndTime(TrackType aType) const {
34   AssertOwnerThread();
35   MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
36   if (aType == TrackInfo::kAudioTrack && mAudioSink) {
37     return mAudioSink->GetEndTime();
38   }
39   return TimeUnit::Zero();
40 }
41 
GetVideoPosition(TimeStamp aNow) const42 TimeUnit AudioSinkWrapper::GetVideoPosition(TimeStamp aNow) const {
43   AssertOwnerThread();
44   MOZ_ASSERT(!mPlayStartTime.IsNull());
45   // Time elapsed since we started playing.
46   double delta = (aNow - mPlayStartTime).ToSeconds();
47   // Take playback rate into account.
48   return mPlayDuration + TimeUnit::FromSeconds(delta * mParams.mPlaybackRate);
49 }
50 
GetPosition(TimeStamp * aTimeStamp) const51 TimeUnit AudioSinkWrapper::GetPosition(TimeStamp* aTimeStamp) const {
52   AssertOwnerThread();
53   MOZ_ASSERT(mIsStarted, "Must be called after playback starts.");
54 
55   TimeUnit pos;
56   TimeStamp t = TimeStamp::Now();
57 
58   if (!mAudioEnded) {
59     MOZ_ASSERT(mAudioSink);
60     // Rely on the audio sink to report playback position when it is not ended.
61     pos = mAudioSink->GetPosition();
62   } else if (!mPlayStartTime.IsNull()) {
63     // Calculate playback position using system clock if we are still playing.
64     pos = GetVideoPosition(t);
65   } else {
66     // Return how long we've played if we are not playing.
67     pos = mPlayDuration;
68   }
69 
70   if (aTimeStamp) {
71     *aTimeStamp = t;
72   }
73 
74   return pos;
75 }
76 
HasUnplayedFrames(TrackType aType) const77 bool AudioSinkWrapper::HasUnplayedFrames(TrackType aType) const {
78   AssertOwnerThread();
79   return mAudioSink ? mAudioSink->HasUnplayedFrames() : false;
80 }
81 
SetVolume(double aVolume)82 void AudioSinkWrapper::SetVolume(double aVolume) {
83   AssertOwnerThread();
84   mParams.mVolume = aVolume;
85   if (mAudioSink) {
86     mAudioSink->SetVolume(aVolume);
87   }
88 }
89 
SetPlaybackRate(double aPlaybackRate)90 void AudioSinkWrapper::SetPlaybackRate(double aPlaybackRate) {
91   AssertOwnerThread();
92   if (!mAudioEnded) {
93     // Pass the playback rate to the audio sink. The underlying AudioStream
94     // will handle playback rate changes and report correct audio position.
95     mAudioSink->SetPlaybackRate(aPlaybackRate);
96   } else if (!mPlayStartTime.IsNull()) {
97     // Adjust playback duration and start time when we are still playing.
98     TimeStamp now = TimeStamp::Now();
99     mPlayDuration = GetVideoPosition(now);
100     mPlayStartTime = now;
101   }
102   // mParams.mPlaybackRate affects GetVideoPosition(). It should be updated
103   // after the calls to GetVideoPosition();
104   mParams.mPlaybackRate = aPlaybackRate;
105 
106   // Do nothing when not playing. Changes in playback rate will be taken into
107   // account by GetVideoPosition().
108 }
109 
SetPreservesPitch(bool aPreservesPitch)110 void AudioSinkWrapper::SetPreservesPitch(bool aPreservesPitch) {
111   AssertOwnerThread();
112   mParams.mPreservesPitch = aPreservesPitch;
113   if (mAudioSink) {
114     mAudioSink->SetPreservesPitch(aPreservesPitch);
115   }
116 }
117 
SetPlaying(bool aPlaying)118 void AudioSinkWrapper::SetPlaying(bool aPlaying) {
119   AssertOwnerThread();
120 
121   // Resume/pause matters only when playback started.
122   if (!mIsStarted) {
123     return;
124   }
125 
126   if (mAudioSink) {
127     mAudioSink->SetPlaying(aPlaying);
128   }
129 
130   if (aPlaying) {
131     MOZ_ASSERT(mPlayStartTime.IsNull());
132     mPlayStartTime = TimeStamp::Now();
133   } else {
134     // Remember how long we've played.
135     mPlayDuration = GetPosition();
136     // mPlayStartTime must be updated later since GetPosition()
137     // depends on the value of mPlayStartTime.
138     mPlayStartTime = TimeStamp();
139   }
140 }
141 
PlaybackRate() const142 double AudioSinkWrapper::PlaybackRate() const {
143   AssertOwnerThread();
144   return mParams.mPlaybackRate;
145 }
146 
Start(const TimeUnit & aStartTime,const MediaInfo & aInfo)147 nsresult AudioSinkWrapper::Start(const TimeUnit& aStartTime,
148                                  const MediaInfo& aInfo) {
149   AssertOwnerThread();
150   MOZ_ASSERT(!mIsStarted, "playback already started.");
151 
152   mIsStarted = true;
153   mPlayDuration = aStartTime;
154   mPlayStartTime = TimeStamp::Now();
155   mAudioEnded = IsAudioSourceEnded(aInfo);
156 
157   nsresult rv = NS_OK;
158   if (!mAudioEnded) {
159     mAudioSink.reset(mCreator->Create());
160     rv = mAudioSink->Init(mParams, mEndedPromise);
161     mEndedPromise
162         ->Then(mOwnerThread.get(), __func__, this,
163                &AudioSinkWrapper::OnAudioEnded, &AudioSinkWrapper::OnAudioEnded)
164         ->Track(mAudioSinkEndedPromise);
165   } else {
166     if (aInfo.HasAudio()) {
167       mEndedPromise = MediaSink::EndedPromise::CreateAndResolve(true, __func__);
168     }
169   }
170   return rv;
171 }
172 
IsAudioSourceEnded(const MediaInfo & aInfo) const173 bool AudioSinkWrapper::IsAudioSourceEnded(const MediaInfo& aInfo) const {
174   // no audio or empty audio queue which won't get data anymore is equivalent to
175   // audio ended
176   return !aInfo.HasAudio() ||
177          (mAudioQueue.IsFinished() && mAudioQueue.GetSize() == 0u);
178 }
179 
Stop()180 void AudioSinkWrapper::Stop() {
181   AssertOwnerThread();
182   MOZ_ASSERT(mIsStarted, "playback not started.");
183 
184   mIsStarted = false;
185   mAudioEnded = true;
186 
187   if (mAudioSink) {
188     mAudioSinkEndedPromise.DisconnectIfExists();
189     mAudioSink->Shutdown();
190     mAudioSink = nullptr;
191     mEndedPromise = nullptr;
192   }
193 }
194 
IsStarted() const195 bool AudioSinkWrapper::IsStarted() const {
196   AssertOwnerThread();
197   return mIsStarted;
198 }
199 
IsPlaying() const200 bool AudioSinkWrapper::IsPlaying() const {
201   AssertOwnerThread();
202   return IsStarted() && !mPlayStartTime.IsNull();
203 }
204 
OnAudioEnded()205 void AudioSinkWrapper::OnAudioEnded() {
206   AssertOwnerThread();
207   mAudioSinkEndedPromise.Complete();
208   mPlayDuration = GetPosition();
209   if (!mPlayStartTime.IsNull()) {
210     mPlayStartTime = TimeStamp::Now();
211   }
212   mAudioEnded = true;
213 }
214 
GetDebugInfo(dom::MediaSinkDebugInfo & aInfo)215 void AudioSinkWrapper::GetDebugInfo(dom::MediaSinkDebugInfo& aInfo) {
216   AssertOwnerThread();
217   aInfo.mAudioSinkWrapper.mIsPlaying = IsPlaying();
218   aInfo.mAudioSinkWrapper.mIsStarted = IsStarted();
219   aInfo.mAudioSinkWrapper.mAudioEnded = mAudioEnded;
220   if (mAudioSink) {
221     mAudioSink->GetDebugInfo(aInfo);
222   }
223 }
224 
225 }  // namespace mozilla
226