avplayer: signal when playback ends

This commit is contained in:
nmzik committed 2026-09-20 01:47:43 +04:00
1 parent 303a0203b6
commit 35a275e97d
1 file changed
+80 -54
+80 -54
View File
@@ -117,6 +117,13 @@ struct AvPlayerEventReplacement {
void* object_pointer = nullptr; void* object_pointer = nullptr;
AvPlayerEventCallback event_callback = nullptr; AvPlayerEventCallback event_callback = nullptr;
}; };
static void emit_event(AvPlayerEventReplacement event, int32_t id, void* data = nullptr) {
if (event.event_callback != nullptr) {
LOGF("\t event_id = %d\n", id);
event.event_callback(event.object_pointer, id, 0, data);
}
}
struct AvPlayerUri { struct AvPlayerUri {
const char* name = nullptr; const char* name = nullptr;
uint32_t length = 0; uint32_t length = 0;
@@ -639,9 +646,9 @@ private:
class Source { class Source {
public: public:
Source(AvPlayerMemAllocator m, AvPlayerFileReplacement f, int32_t video_buffers, bool vdec2, Source(AvPlayerMemAllocator m, AvPlayerFileReplacement f, AvPlayerEventReplacement e,
uint32_t demux_buffer_size) int32_t video_buffers, bool vdec2, uint32_t demux_buffer_size)
: mem(m), file(f), : mem(m), file(f), event(e),
max_video_buffers(std::clamp(video_buffers <= 0 ? 2 : video_buffers, 2, 16)), max_video_buffers(std::clamp(video_buffers <= 0 ? 2 : video_buffers, 2, 16)),
use_vdec2(vdec2), use_vdec2(vdec2),
video_packets( video_packets(
@@ -817,21 +824,26 @@ public:
return result; return result;
} }
int Stop() { int Stop() {
std::scoped_lock lifecycle_lock(lifecycle_mutex); bool was_playing_video = false;
bool was_playing_video = false; AvPlayerEventReplacement stop_event;
{ {
std::lock_guard lock(mutex); std::scoped_lock lifecycle_lock(lifecycle_mutex);
was_playing_video = !stopped && video_id.has_value(); {
stopped = true; std::lock_guard lock(mutex);
} was_playing_video = !stopped && video_id.has_value();
StopWorkers(); stopped = true;
{ }
std::lock_guard lock(mutex); StopWorkers();
ResetNoLock(); {
std::lock_guard lock(mutex);
ResetNoLock();
}
stop_event = TakeStopEvent();
} }
if (was_playing_video) { if (was_playing_video) {
::printf("AvPlayer video stopped\n"); ::printf("AvPlayer video stopped\n");
} }
emit_event(stop_event, AVPLAYER_EVENT_STATE_STOP);
return 0; return 0;
} }
void Pause() { void Pause() {
@@ -860,9 +872,7 @@ public:
} }
bool Active() const { bool Active() const {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
return !stopped && !pipeline_failed && return !stopped && !pipeline_failed && !DrainedNoLock();
(!demux_eof || !video_done || !audio_done || !video_frames.Empty() ||
!audio_frames.Empty());
} }
uint64_t CurrentTime() const { uint64_t CurrentTime() const {
std::lock_guard lock(mutex); std::lock_guard lock(mutex);
@@ -945,6 +955,7 @@ public:
current_video = std::move(*frame); current_video = std::move(*frame);
*out = current_video->info; *out = current_video->info;
RecordLoopBoundary(*current_video); RecordLoopBoundary(*current_video);
completion_changed.notify_all();
if (deliver_seek_frame) { if (deliver_seek_frame) {
seek_video_frame_pending = false; seek_video_frame_pending = false;
} }
@@ -976,6 +987,7 @@ public:
current_audio->info.details.audio.language_code, 4); current_audio->info.details.audio.language_code, 4);
last_audio_ts = out->time_stamp; last_audio_ts = out->time_stamp;
RecordLoopBoundary(*current_audio); RecordLoopBoundary(*current_audio);
completion_changed.notify_all();
return true; return true;
} }
std::optional<int32_t> TakeWarning() { std::optional<int32_t> TakeWarning() {
@@ -988,6 +1000,13 @@ public:
} }
private: private:
bool DrainedNoLock() const {
return demux_eof && video_done && audio_done && video_frames.Empty() &&
audio_frames.Empty();
}
AvPlayerEventReplacement TakeStopEvent() {
return stop_event_sent.exchange(true) ? AvPlayerEventReplacement {} : event;
}
void RecordLoopBoundary(const ReadyFrame& frame) { void RecordLoopBoundary(const ReadyFrame& frame) {
if (frame.timestamp_offset > last_output_loop_offset) { if (frame.timestamp_offset > last_output_loop_offset) {
last_output_loop_offset = frame.timestamp_offset; last_output_loop_offset = frame.timestamp_offset;
@@ -1002,23 +1021,17 @@ private:
streamer.reset(); streamer.reset();
} }
void ResetNoLock(bool retain_output_buffers = false) { void ResetNoLock(bool retain_output_buffers = false) {
std::optional<ReadyFrame> delivered_video;
std::optional<ReadyFrame> delivered_audio;
std::deque<std::unique_ptr<GuestBuffer>> in_flight_video;
if (retain_output_buffers) {
delivered_video = std::move(current_video);
delivered_audio = std::move(current_audio);
in_flight_video = std::move(retired_video);
}
video_packets.Clear(); video_packets.Clear();
audio_packets.Clear(); audio_packets.Clear();
video_frames.Clear(); video_frames.Clear();
audio_frames.Clear(); audio_frames.Clear();
video_buffers.Clear(); video_buffers.Clear();
audio_buffers.Clear(); audio_buffers.Clear();
retired_video.clear(); if (!retain_output_buffers) {
current_video.reset(); retired_video.clear();
current_audio.reset(); current_video.reset();
current_audio.reset();
}
if (sws != nullptr) { if (sws != nullptr) {
sws_freeContext(sws); sws_freeContext(sws);
sws = nullptr; sws = nullptr;
@@ -1042,9 +1055,6 @@ private:
last_audio_ts = 0; last_audio_ts = 0;
last_output_loop_offset = 0; last_output_loop_offset = 0;
pending_loop_warnings = 0; pending_loop_warnings = 0;
current_video = std::move(delivered_video);
current_audio = std::move(delivered_audio);
retired_video = std::move(in_flight_video);
} }
void AutoEnable() { void AutoEnable() {
for (uint32_t i = 0; i < fmt->nb_streams; i++) { for (uint32_t i = 0; i < fmt->nb_streams; i++) {
@@ -1174,6 +1184,7 @@ private:
return true; return true;
} }
bool StartWorkers() { bool StartWorkers() {
std::unique_lock lock(mutex);
worker_stop = false; worker_stop = false;
interrupt_io = false; interrupt_io = false;
pipeline_failed = false; pipeline_failed = false;
@@ -1183,20 +1194,26 @@ private:
if ((video_id && !CreateWorker(video_thread, VideoDecoderEntry, "AvPlayerVideoDecoder")) || if ((video_id && !CreateWorker(video_thread, VideoDecoderEntry, "AvPlayerVideoDecoder")) ||
(audio_id && !CreateWorker(audio_thread, AudioDecoderEntry, "AvPlayerAudioDecoder")) || (audio_id && !CreateWorker(audio_thread, AudioDecoderEntry, "AvPlayerAudioDecoder")) ||
!CreateWorker(demux_thread, DemuxEntry, "AvPlayerDemuxer")) { !CreateWorker(demux_thread, DemuxEntry, "AvPlayerDemuxer")) {
lock.unlock();
StopWorkers(); StopWorkers();
return false; return false;
} }
stop_event_sent = false;
return true; return true;
} }
void NotifyWorkers() { void NotifyWorkers() {
completion_changed.notify_all();
video_packets.Notify(); video_packets.Notify();
audio_packets.Notify(); audio_packets.Notify();
video_buffers.Notify(); video_buffers.Notify();
audio_buffers.Notify(); audio_buffers.Notify();
} }
void StopWorkers() { void StopWorkers() {
worker_stop = true; {
interrupt_io = true; std::lock_guard lock(mutex);
worker_stop = true;
interrupt_io = true;
}
NotifyWorkers(); NotifyWorkers();
if (demux_thread != nullptr) { if (demux_thread != nullptr) {
LibKernel::PthreadJoin(demux_thread, nullptr); LibKernel::PthreadJoin(demux_thread, nullptr);
@@ -1212,9 +1229,12 @@ private:
} }
} }
void FailPipeline() { void FailPipeline() {
pipeline_failed = true; {
worker_stop = true; std::lock_guard lock(mutex);
interrupt_io = true; pipeline_failed = true;
worker_stop = true;
interrupt_io = true;
}
NotifyWorkers(); NotifyWorkers();
} }
void Demux() { void Demux() {
@@ -1269,6 +1289,13 @@ private:
demux_eof = true; demux_eof = true;
video_packets.Notify(); video_packets.Notify();
audio_packets.Notify(); audio_packets.Notify();
std::unique_lock lock(mutex);
completion_changed.wait(lock, [&] { return worker_stop || DrainedNoLock(); });
if (!worker_stop) {
const auto stop_event = TakeStopEvent();
lock.unlock();
emit_event(stop_event, AVPLAYER_EVENT_STATE_STOP);
}
} }
void VideoDecoder() { void VideoDecoder() {
Decoder(video_packets, video_ctx, video_buffers, video_frames, video_done, Decoder(video_packets, video_ctx, video_buffers, video_frames, video_done,
@@ -1316,7 +1343,11 @@ private:
break; break;
} }
} }
done = true; {
std::lock_guard lock(mutex);
done = true;
}
completion_changed.notify_all();
} }
bool DecodePacket(AVCodecContext* codec, AVPacket* packet, uint64_t timestamp_offset, bool DecodePacket(AVCodecContext* codec, AVPacket* packet, uint64_t timestamp_offset,
WorkQueue<std::unique_ptr<GuestBuffer>>& buffers, WorkQueue<std::unique_ptr<GuestBuffer>>& buffers,
@@ -1607,6 +1638,7 @@ private:
} }
AvPlayerMemAllocator mem; AvPlayerMemAllocator mem;
AvPlayerFileReplacement file; AvPlayerFileReplacement file;
AvPlayerEventReplacement event;
int max_video_buffers = 2; int max_video_buffers = 2;
bool use_vdec2 = false; bool use_vdec2 = false;
AvPlayerSourceType source_type = AvPlayerSourceUnknown; AvPlayerSourceType source_type = AvPlayerSourceUnknown;
@@ -1640,6 +1672,8 @@ private:
std::atomic_bool demux_eof {true}; std::atomic_bool demux_eof {true};
std::atomic_bool video_done {true}; std::atomic_bool video_done {true};
std::atomic_bool audio_done {true}; std::atomic_bool audio_done {true};
std::atomic_bool stop_event_sent {true};
std::condition_variable completion_changed;
std::mutex lifecycle_mutex; std::mutex lifecycle_mutex;
mutable std::mutex mutex; mutable std::mutex mutex;
bool stopped = true; bool stopped = true;
@@ -1673,19 +1707,13 @@ struct AvPlayerInternal {
static bool valid_allocators(const AvPlayerMemAllocator& m) { static bool valid_allocators(const AvPlayerMemAllocator& m) {
return m.allocate && m.deallocate && m.allocate_texture && m.deallocate_texture; return m.allocate && m.deallocate && m.allocate_texture && m.deallocate_texture;
} }
static void emit_event(AvPlayerInternal* h, int32_t id, void* data = nullptr) {
if (h != nullptr && h->event.event_callback != nullptr) {
LOGF("\t event_id = %d\n", id);
h->event.event_callback(h->event.object_pointer, id, 0, data);
}
}
static void pump_warnings(AvPlayerInternal* h) { static void pump_warnings(AvPlayerInternal* h) {
if (h == nullptr || h->source == nullptr) { if (h == nullptr || h->source == nullptr) {
return; return;
} }
while (auto w = h->source->TakeWarning()) { while (auto w = h->source->TakeWarning()) {
int32_t warning = *w; int32_t warning = *w;
emit_event(h, AVPLAYER_EVENT_WARNING_ID, &warning); emit_event(h->event, AVPLAYER_EVENT_WARNING_ID, &warning);
} }
} }
static AvPlayerInternal* create_player(const AvPlayerMemAllocator& mem, static AvPlayerInternal* create_player(const AvPlayerMemAllocator& mem,
@@ -1713,17 +1741,17 @@ static int add_source(AvPlayerInternal* h, const std::string& filename, AvPlayer
} }
bool vdec2 = bool vdec2 =
h->post_init.video_decoder_init.decoder_type.video_type == AvPlayerVideoDecoderSoftware2; h->post_init.video_decoder_init.decoder_type.video_type == AvPlayerVideoDecoderSoftware2;
auto s = std::make_unique<Source>(h->mem, h->file, h->video_buffers, vdec2, auto s = std::make_unique<Source>(h->mem, h->file, h->event, h->video_buffers, vdec2,
h->post_init.demux_video_buffer_size); h->post_init.demux_video_buffer_size);
if (auto rc = s->Init(filename, type); rc < 0) { if (auto rc = s->Init(filename, type); rc < 0) {
return rc; return rc;
} }
h->source = std::move(s); h->source = std::move(s);
emit_event(h, AVPLAYER_EVENT_STATE_READY); emit_event(h->event, AVPLAYER_EVENT_STATE_READY);
if (h->auto_start) { if (h->auto_start) {
auto rc = h->source->Start(); auto rc = h->source->Start();
if (rc == 0) { if (rc == 0) {
emit_event(h, AVPLAYER_EVENT_STATE_PLAY); emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
} }
return rc; return rc;
} }
@@ -1872,7 +1900,7 @@ int KYTY_SYSV_ABI AvPlayerStart(AvPlayerInternal* h) {
} }
auto rc = h->source->Start(); auto rc = h->source->Start();
if (rc == 0) { if (rc == 0) {
emit_event(h, AVPLAYER_EVENT_STATE_PLAY); emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
} }
return rc; return rc;
} }
@@ -1887,7 +1915,7 @@ int KYTY_SYSV_ABI AvPlayerStartEx(AvPlayerInternal* h, const void* start_info_ex
: static_cast<const AvPlayerStartInfoEx*>(start_info_ex)->start_time_milliseconds; : static_cast<const AvPlayerStartInfoEx*>(start_info_ex)->start_time_milliseconds;
auto rc = h->source->Start(ms); auto rc = h->source->Start(ms);
if (rc == 0) { if (rc == 0) {
emit_event(h, AVPLAYER_EVENT_STATE_PLAY); emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
} }
return rc; return rc;
} }
@@ -1896,9 +1924,7 @@ int KYTY_SYSV_ABI AvPlayerStop(AvPlayerInternal* h) {
if (h == nullptr || h->source == nullptr) { if (h == nullptr || h->source == nullptr) {
return AVPLAYER_ERROR_INVALID_PARAMS; return AVPLAYER_ERROR_INVALID_PARAMS;
} }
auto rc = h->source->Stop(); return h->source->Stop();
emit_event(h, AVPLAYER_EVENT_STATE_STOP);
return rc;
} }
int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) { int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) {
PRINT_NAME(); PRINT_NAME();
@@ -1906,7 +1932,7 @@ int KYTY_SYSV_ABI AvPlayerPause(AvPlayerInternal* h) {
return AVPLAYER_ERROR_INVALID_PARAMS; return AVPLAYER_ERROR_INVALID_PARAMS;
} }
h->source->Pause(); h->source->Pause();
emit_event(h, AVPLAYER_EVENT_STATE_PAUSE); emit_event(h->event, AVPLAYER_EVENT_STATE_PAUSE);
return 0; return 0;
} }
int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) { int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
@@ -1915,7 +1941,7 @@ int KYTY_SYSV_ABI AvPlayerResume(AvPlayerInternal* h) {
return AVPLAYER_ERROR_INVALID_PARAMS; return AVPLAYER_ERROR_INVALID_PARAMS;
} }
h->source->Resume(); h->source->Resume();
emit_event(h, AVPLAYER_EVENT_STATE_PLAY); emit_event(h->event, AVPLAYER_EVENT_STATE_PLAY);
return 0; return 0;
} }
int KYTY_SYSV_ABI AvPlayerSetLooping(AvPlayerInternal* h, Bool loop) { int KYTY_SYSV_ABI AvPlayerSetLooping(AvPlayerInternal* h, Bool loop) {
@@ -2017,7 +2043,7 @@ int KYTY_SYSV_ABI AvPlayerJumpToTime(AvPlayerInternal* h, uint64_t time_ms) {
auto rc = h->source->Jump(time_ms); auto rc = h->source->Jump(time_ms);
if (rc == 0) { if (rc == 0) {
int32_t w = AVPLAYER_WARNING_JUMP_COMPLETE; int32_t w = AVPLAYER_WARNING_JUMP_COMPLETE;
emit_event(h, AVPLAYER_EVENT_WARNING_ID, &w); emit_event(h->event, AVPLAYER_EVENT_WARNING_ID, &w);
} }
return rc; return rc;
} }