tdesktop/Telegram/SourceFiles/media/streaming/media_streaming_audio_track...

223 lines
5.3 KiB
C++
Raw Normal View History

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "media/streaming/media_streaming_audio_track.h"
#include "media/streaming/media_streaming_utility.h"
#include "media/audio/media_audio.h"
#include "media/audio/media_child_ffmpeg_loader.h"
#include "media/player/media_player_instance.h"
namespace Media {
namespace Streaming {
AudioTrack::AudioTrack(
const PlaybackOptions &options,
Stream &&stream,
2019-02-21 11:15:44 +00:00
AudioMsgId audioId,
FnMut<void(const Information &)> ready,
2019-03-05 13:56:27 +00:00
Fn<void(Error)> error)
: _options(options)
, _stream(std::move(stream))
2019-02-21 11:15:44 +00:00
, _audioId(audioId)
, _ready(std::move(ready))
, _error(std::move(error))
, _playPosition(options.position) {
Expects(_stream.duration > 1);
Expects(_ready != nullptr);
Expects(_error != nullptr);
2019-02-28 21:03:25 +00:00
Expects(_audioId.externalPlayId() != 0);
}
int AudioTrack::streamIndex() const {
// Thread-safe, because _stream.index is immutable.
return _stream.index;
}
AVRational AudioTrack::streamTimeBase() const {
return _stream.timeBase;
}
crl::time AudioTrack::streamDuration() const {
return _stream.duration;
}
void AudioTrack::process(Packet &&packet) {
_noMoreData = packet.empty();
2019-02-21 11:15:44 +00:00
if (initialized()) {
mixerEnqueue(std::move(packet));
} else if (!tryReadFirstFrame(std::move(packet))) {
2019-03-05 13:56:27 +00:00
_error(Error::InvalidData);
}
}
void AudioTrack::waitForData() {
if (initialized()) {
mixerForceToBuffer();
}
}
2019-02-21 11:15:44 +00:00
bool AudioTrack::initialized() const {
return !_ready;
}
bool AudioTrack::tryReadFirstFrame(Packet &&packet) {
if (ProcessPacket(_stream, std::move(packet)).failed()) {
return false;
}
if (const auto error = ReadNextFrame(_stream)) {
if (error.code() == AVERROR_EOF) {
2019-02-22 12:39:32 +00:00
// Return the last valid frame if we seek too far.
return processFirstFrame();
} else if (error.code() != AVERROR(EAGAIN) || _noMoreData) {
return false;
2019-02-22 12:39:32 +00:00
} else {
// Waiting for more packets.
return true;
}
} else if (!fillStateFromFrame()) {
return false;
2019-02-22 12:39:32 +00:00
} else if (_startedPosition < _options.position) {
// Seek was with AVSEEK_FLAG_BACKWARD so first we get old frames.
// Try skipping frames until one is after the requested position.
return true;
} else {
return processFirstFrame();
}
2019-02-22 12:39:32 +00:00
}
bool AudioTrack::processFirstFrame() {
mixerInit();
callReady();
return true;
}
bool AudioTrack::fillStateFromFrame() {
2019-02-22 12:39:32 +00:00
const auto position = FramePosition(_stream);
if (position == kTimeUnknown) {
return false;
}
_startedPosition = position;
return true;
}
void AudioTrack::mixerInit() {
2019-02-21 11:15:44 +00:00
Expects(!initialized());
2019-02-28 21:03:25 +00:00
auto data = std::make_unique<ExternalSoundData>();
data->frame = std::move(_stream.frame);
data->codec = std::move(_stream.codec);
data->frequency = _stream.frequency;
data->length = (_stream.duration * data->frequency) / 1000LL;
data->speed = _options.speed;
2019-02-28 21:03:25 +00:00
Media::Player::mixer()->play(
2019-02-21 11:15:44 +00:00
_audioId,
std::move(data),
_startedPosition);
}
void AudioTrack::callReady() {
Expects(_ready != nullptr);
auto data = AudioInformation();
data.state.duration = _stream.duration;
data.state.position = _startedPosition;
data.state.receivedTill = _noMoreData
? _stream.duration
: _startedPosition;
base::take(_ready)({ VideoInformation(), data });
}
void AudioTrack::mixerEnqueue(Packet &&packet) {
2019-02-28 21:03:25 +00:00
Media::Player::mixer()->feedFromExternal({
_audioId,
std::move(packet)
});
}
void AudioTrack::mixerForceToBuffer() {
2019-02-28 21:03:25 +00:00
Media::Player::mixer()->forceToBufferExternal(_audioId);
}
2019-02-21 14:57:00 +00:00
void AudioTrack::pause(crl::time time) {
Expects(initialized());
Media::Player::mixer()->pause(_audioId, true);
}
void AudioTrack::resume(crl::time time) {
2019-02-21 11:15:44 +00:00
Expects(initialized());
2019-02-21 11:15:44 +00:00
Media::Player::mixer()->resume(_audioId, true);
}
2019-02-21 16:01:55 +00:00
void AudioTrack::setSpeed(float64 speed) {
_options.speed = speed;
2019-02-28 21:03:25 +00:00
Media::Player::mixer()->setSpeedFromExternal(_audioId, speed);
2019-02-21 16:01:55 +00:00
}
rpl::producer<> AudioTrack::waitingForData() const {
return _waitingForData.events();
}
rpl::producer<crl::time> AudioTrack::playPosition() {
Expects(_ready == nullptr);
if (!_subscription) {
_subscription = Media::Player::Updated(
).add_subscription([=](const AudioMsgId &id) {
using State = Media::Player::State;
2019-02-21 11:15:44 +00:00
if (id != _audioId) {
return;
}
2019-02-28 21:03:25 +00:00
const auto state = Media::Player::mixer()->currentState(
_audioId.type());
2019-02-21 11:15:44 +00:00
if (state.id != _audioId) {
2019-02-25 10:17:25 +00:00
// #TODO streaming later muted by other
return;
} else switch (state.state) {
case State::Stopped:
case State::StoppedAtEnd:
case State::PausedAtEnd:
_playPosition.reset();
return;
case State::StoppedAtError:
case State::StoppedAtStart:
2019-03-05 13:56:27 +00:00
_error(Error::InvalidData);
return;
case State::Starting:
case State::Playing:
case State::Stopping:
case State::Pausing:
case State::Resuming:
if (state.waitingForData) {
_waitingForData.fire({});
}
_playPosition = std::clamp(
2019-03-05 15:24:55 +00:00
crl::time((state.position * 1000 + (state.frequency / 2))
/ state.frequency),
crl::time(0),
_stream.duration - 1);
return;
case State::Paused:
return;
}
});
}
return _playPosition.value();
}
AudioTrack::~AudioTrack() {
2019-02-28 21:03:25 +00:00
if (_audioId.externalPlayId()) {
2019-02-21 11:15:44 +00:00
Media::Player::mixer()->stop(_audioId);
}
}
} // namespace Streaming
} // namespace Media