1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "media/streaming/media_streaming_audio_track.h"
9
10 #include "media/streaming/media_streaming_utility.h"
11 #include "media/audio/media_audio.h"
12 #include "media/audio/media_child_ffmpeg_loader.h"
13 #include "media/player/media_player_instance.h"
14
15 namespace Media {
16 namespace Streaming {
17
AudioTrack(const PlaybackOptions & options,Stream && stream,AudioMsgId audioId,FnMut<void (const Information &)> ready,Fn<void (Error)> error)18 AudioTrack::AudioTrack(
19 const PlaybackOptions &options,
20 Stream &&stream,
21 AudioMsgId audioId,
22 FnMut<void(const Information &)> ready,
23 Fn<void(Error)> error)
24 : _options(options)
25 , _stream(std::move(stream))
26 , _audioId(audioId)
27 , _ready(std::move(ready))
28 , _error(std::move(error))
29 , _playPosition(options.position) {
30 Expects(_stream.duration > 1);
31 Expects(_stream.duration != kDurationUnavailable); // Not supported.
32 Expects(_ready != nullptr);
33 Expects(_error != nullptr);
34 Expects(_audioId.externalPlayId() != 0);
35 }
36
streamIndex() const37 int AudioTrack::streamIndex() const {
38 // Thread-safe, because _stream.index is immutable.
39 return _stream.index;
40 }
41
streamTimeBase() const42 AVRational AudioTrack::streamTimeBase() const {
43 return _stream.timeBase;
44 }
45
streamDuration() const46 crl::time AudioTrack::streamDuration() const {
47 return _stream.duration;
48 }
49
process(std::vector<FFmpeg::Packet> && packets)50 void AudioTrack::process(std::vector<FFmpeg::Packet> &&packets) {
51 if (packets.empty()) {
52 return;
53 } else if (packets.front().empty()) {
54 Assert(packets.size() == 1);
55 _readTillEnd = true;
56 }
57 for (auto i = begin(packets), e = end(packets); i != e; ++i) {
58 if (initialized()) {
59 mixerEnqueue(gsl::make_span(&*i, (e - i)));
60 break;
61 } else if (!tryReadFirstFrame(std::move(*i))) {
62 _error(Error::InvalidData);
63 break;
64 }
65 }
66 }
67
waitForData()68 void AudioTrack::waitForData() {
69 if (initialized()) {
70 mixerForceToBuffer();
71 }
72 }
73
initialized() const74 bool AudioTrack::initialized() const {
75 return !_ready;
76 }
77
tryReadFirstFrame(FFmpeg::Packet && packet)78 bool AudioTrack::tryReadFirstFrame(FFmpeg::Packet &&packet) {
79 if (ProcessPacket(_stream, std::move(packet)).failed()) {
80 return false;
81 }
82 while (true) {
83 if (const auto error = ReadNextFrame(_stream)) {
84 if (error.code() == AVERROR_EOF) {
85 if (!_initialSkippingFrame) {
86 return false;
87 }
88 // Return the last valid frame if we seek too far.
89 _stream.frame = std::move(_initialSkippingFrame);
90 return processFirstFrame();
91 } else if (error.code() != AVERROR(EAGAIN) || _readTillEnd) {
92 return false;
93 } else {
94 // Waiting for more packets.
95 return true;
96 }
97 } else if (!fillStateFromFrame()) {
98 return false;
99 } else if (_startedPosition >= _options.position) {
100 return processFirstFrame();
101 }
102
103 // Seek was with AVSEEK_FLAG_BACKWARD so first we get old frames.
104 // Try skipping frames until one is after the requested position.
105 std::swap(_initialSkippingFrame, _stream.frame);
106 if (!_stream.frame) {
107 _stream.frame = FFmpeg::MakeFramePointer();
108 }
109 }
110 }
111
processFirstFrame()112 bool AudioTrack::processFirstFrame() {
113 if (!FFmpeg::FrameHasData(_stream.frame.get())) {
114 return false;
115 }
116 mixerInit();
117 callReady();
118 return true;
119 }
120
fillStateFromFrame()121 bool AudioTrack::fillStateFromFrame() {
122 const auto position = FramePosition(_stream);
123 if (position == kTimeUnknown) {
124 return false;
125 }
126 _startedPosition = position;
127 return true;
128 }
129
mixerInit()130 void AudioTrack::mixerInit() {
131 Expects(!initialized());
132
133 auto data = std::make_unique<ExternalSoundData>();
134 data->frame = std::move(_stream.frame);
135 data->codec = std::move(_stream.codec);
136 data->frequency = _stream.frequency;
137 data->length = (_stream.duration * data->frequency) / 1000LL;
138 data->speed = _options.speed;
139
140 Media::Player::mixer()->play(
141 _audioId,
142 std::move(data),
143 _startedPosition);
144 }
145
callReady()146 void AudioTrack::callReady() {
147 Expects(_ready != nullptr);
148
149 auto data = AudioInformation();
150 data.state.duration = _stream.duration;
151 data.state.position = _startedPosition;
152 data.state.receivedTill = _readTillEnd
153 ? _stream.duration
154 : _startedPosition;
155 base::take(_ready)({ VideoInformation(), data });
156 }
157
mixerEnqueue(gsl::span<FFmpeg::Packet> packets)158 void AudioTrack::mixerEnqueue(gsl::span<FFmpeg::Packet> packets) {
159 Media::Player::mixer()->feedFromExternal({
160 _audioId,
161 packets
162 });
163 }
164
mixerForceToBuffer()165 void AudioTrack::mixerForceToBuffer() {
166 Media::Player::mixer()->forceToBufferExternal(_audioId);
167 }
168
pause(crl::time time)169 void AudioTrack::pause(crl::time time) {
170 Expects(initialized());
171
172 Media::Player::mixer()->pause(_audioId, true);
173 }
174
resume(crl::time time)175 void AudioTrack::resume(crl::time time) {
176 Expects(initialized());
177
178 Media::Player::mixer()->resume(_audioId, true);
179 }
180
stop()181 void AudioTrack::stop() {
182 if (_audioId.externalPlayId()) {
183 Media::Player::mixer()->stop(_audioId);
184 }
185 }
186
setSpeed(float64 speed)187 void AudioTrack::setSpeed(float64 speed) {
188 _options.speed = speed;
189 Media::Player::mixer()->setSpeedFromExternal(_audioId, speed);
190 }
191
waitingForData() const192 rpl::producer<> AudioTrack::waitingForData() const {
193 return _waitingForData.events();
194 }
195
playPosition()196 rpl::producer<crl::time> AudioTrack::playPosition() {
197 Expects(_ready == nullptr);
198
199 if (!_subscription) {
200 _subscription = Media::Player::Updated(
201 ).add_subscription([=](const AudioMsgId &id) {
202 using State = Media::Player::State;
203 if (id != _audioId) {
204 return;
205 }
206 const auto state = Media::Player::mixer()->currentState(
207 _audioId.type());
208 if (state.id != _audioId) {
209 // #TODO streaming later muted by other
210 return;
211 } else switch (state.state) {
212 case State::Stopped:
213 case State::StoppedAtEnd:
214 case State::PausedAtEnd:
215 _playPosition.reset();
216 return;
217 case State::StoppedAtError:
218 case State::StoppedAtStart:
219 _error(Error::InvalidData);
220 return;
221 case State::Starting:
222 case State::Playing:
223 case State::Stopping:
224 case State::Pausing:
225 case State::Resuming:
226 if (state.waitingForData) {
227 _waitingForData.fire({});
228 }
229 _playPosition = std::clamp(
230 crl::time((state.position * 1000 + (state.frequency / 2))
231 / state.frequency),
232 crl::time(0),
233 _stream.duration - 1);
234 return;
235 case State::Paused:
236 return;
237 }
238 });
239 }
240 return _playPosition.value();
241 }
242
~AudioTrack()243 AudioTrack::~AudioTrack() {
244 stop();
245 }
246
247 } // namespace Streaming
248 } // namespace Media
249