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