Coverage Report

Created: 2026-10-28 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibMedia/PlaybackManager.h
Line
Count
Source
1
/*
2
 * Copyright (c) 2022, Gregory Bertilson <zaggy1024@gmail.com>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#pragma once
8
9
#include <AK/Atomic.h>
10
#include <AK/Function.h>
11
#include <AK/NonnullOwnPtr.h>
12
#include <AK/Queue.h>
13
#include <AK/Time.h>
14
#include <LibCore/SharedCircularQueue.h>
15
#include <LibGfx/Bitmap.h>
16
#include <LibMedia/Containers/Matroska/Document.h>
17
#include <LibMedia/Demuxer.h>
18
#include <LibThreading/ConditionVariable.h>
19
#include <LibThreading/Mutex.h>
20
#include <LibThreading/Thread.h>
21
22
#include "VideoDecoder.h"
23
24
namespace Media {
25
26
class FrameQueueItem {
27
public:
28
    FrameQueueItem()
29
0
        : m_data(Empty())
30
0
        , m_timestamp(Duration::zero())
31
0
    {
32
0
    }
33
34
    static constexpr Duration no_timestamp = Duration::min();
35
36
    enum class Type {
37
        Frame,
38
        Error,
39
    };
40
41
    static FrameQueueItem frame(RefPtr<Gfx::Bitmap> bitmap, Duration timestamp)
42
0
    {
43
0
        return FrameQueueItem(move(bitmap), timestamp);
44
0
    }
45
46
    static FrameQueueItem error_marker(DecoderError&& error, Duration timestamp)
47
0
    {
48
0
        return FrameQueueItem(move(error), timestamp);
49
0
    }
50
51
0
    bool is_frame() const { return m_data.has<RefPtr<Gfx::Bitmap>>(); }
52
0
    RefPtr<Gfx::Bitmap> bitmap() const { return m_data.get<RefPtr<Gfx::Bitmap>>(); }
53
0
    Duration timestamp() const { return m_timestamp; }
54
55
0
    bool is_error() const { return m_data.has<DecoderError>(); }
56
0
    DecoderError const& error() const { return m_data.get<DecoderError>(); }
57
    DecoderError release_error()
58
0
    {
59
0
        auto error = move(m_data.get<DecoderError>());
60
0
        m_data.set(Empty());
61
0
        return error;
62
0
    }
63
64
0
    bool is_empty() const { return m_data.has<Empty>(); }
65
66
    ByteString debug_string() const
67
0
    {
68
0
        if (is_error())
69
0
            return ByteString::formatted("{} at {}ms", error().string_literal(), timestamp().to_milliseconds());
70
0
        return ByteString::formatted("frame at {}ms", timestamp().to_milliseconds());
71
0
    }
72
73
private:
74
    FrameQueueItem(RefPtr<Gfx::Bitmap> bitmap, Duration timestamp)
75
0
        : m_data(move(bitmap))
76
0
        , m_timestamp(timestamp)
77
0
    {
78
0
        VERIFY(m_timestamp != no_timestamp);
79
0
    }
80
81
    FrameQueueItem(DecoderError&& error, Duration timestamp)
82
0
        : m_data(move(error))
83
0
        , m_timestamp(timestamp)
84
0
    {
85
0
    }
86
87
    Variant<Empty, RefPtr<Gfx::Bitmap>, DecoderError> m_data { Empty() };
88
    Duration m_timestamp { no_timestamp };
89
};
90
91
static constexpr size_t frame_buffer_count = 4;
92
using VideoFrameQueue = Core::SharedSingleProducerCircularQueue<FrameQueueItem, frame_buffer_count>;
93
94
enum class PlaybackState {
95
    Playing,
96
    Paused,
97
    Buffering,
98
    Seeking,
99
    Stopped,
100
};
101
102
class PlaybackManager {
103
    AK_MAKE_NONCOPYABLE(PlaybackManager);
104
    AK_MAKE_NONMOVABLE(PlaybackManager);
105
106
public:
107
    enum class SeekMode {
108
        Accurate,
109
        Fast,
110
    };
111
112
    static constexpr SeekMode DEFAULT_SEEK_MODE = SeekMode::Accurate;
113
114
    static DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> from_file(StringView file);
115
    static DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> from_mapped_file(NonnullOwnPtr<Core::MappedFile> file);
116
117
    static DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> from_data(ReadonlyBytes data);
118
119
    PlaybackManager(NonnullOwnPtr<Demuxer>& demuxer, Track video_track, NonnullOwnPtr<VideoDecoder>&& decoder, VideoFrameQueue&& frame_queue);
120
    ~PlaybackManager();
121
122
    void resume_playback();
123
    void pause_playback();
124
    void restart_playback();
125
    void terminate_playback();
126
    void seek_to_timestamp(Duration, SeekMode = DEFAULT_SEEK_MODE);
127
    bool is_playing() const
128
0
    {
129
0
        return m_playback_handler->is_playing();
130
0
    }
131
    PlaybackState get_state() const
132
0
    {
133
0
        return m_playback_handler->get_state();
134
0
    }
135
136
0
    u64 number_of_skipped_frames() const { return m_skipped_frames; }
137
138
    Duration current_playback_time();
139
    Duration duration();
140
141
    Function<void(RefPtr<Gfx::Bitmap>)> on_video_frame;
142
    Function<void()> on_playback_state_change;
143
    Function<void(DecoderError)> on_decoder_error;
144
    Function<void(Error)> on_fatal_playback_error;
145
146
0
    Track const& selected_video_track() const { return m_selected_video_track; }
147
148
private:
149
    class PlaybackStateHandler;
150
    // Abstract class to allow resuming play/pause after the state is completed.
151
    class ResumingStateHandler;
152
    class PlayingStateHandler;
153
    class PausedStateHandler;
154
    class BufferingStateHandler;
155
    class SeekingStateHandler;
156
    class StoppedStateHandler;
157
158
    static DecoderErrorOr<NonnullOwnPtr<PlaybackManager>> create(NonnullOwnPtr<Demuxer> demuxer);
159
160
    void timer_callback();
161
    // This must be called with m_demuxer_mutex locked!
162
    DecoderErrorOr<Optional<Duration>> seek_demuxer_to_most_recent_keyframe(Duration timestamp, Optional<Duration> earliest_available_sample = OptionalNone());
163
164
    Optional<FrameQueueItem> dequeue_one_frame();
165
    void set_state_update_timer(int delay_ms);
166
167
    void decode_and_queue_one_sample();
168
169
    void dispatch_decoder_error(DecoderError error);
170
    void dispatch_new_frame(RefPtr<Gfx::Bitmap> frame);
171
    // Returns whether we changed playback states. If so, any PlaybackStateHandler processing must cease.
172
    [[nodiscard]] bool dispatch_frame_queue_item(FrameQueueItem&&);
173
    void dispatch_state_change();
174
    void dispatch_fatal_error(Error);
175
176
    Duration m_last_present_in_media_time = Duration::zero();
177
178
    NonnullOwnPtr<Demuxer> m_demuxer;
179
    Threading::Mutex m_decoder_mutex;
180
    Track m_selected_video_track;
181
182
    VideoFrameQueue m_frame_queue;
183
184
    RefPtr<Core::Timer> m_state_update_timer;
185
    unsigned m_decoding_buffer_time_ms = 16;
186
187
    RefPtr<Threading::Thread> m_decode_thread;
188
    NonnullOwnPtr<VideoDecoder> m_decoder;
189
    Atomic<bool> m_stop_decoding { false };
190
    Threading::Mutex m_decode_wait_mutex;
191
    Threading::ConditionVariable m_decode_wait_condition;
192
    Atomic<bool> m_buffer_is_full { false };
193
194
    OwnPtr<PlaybackStateHandler> m_playback_handler;
195
    Optional<FrameQueueItem> m_next_frame;
196
197
    u64 m_skipped_frames { 0 };
198
199
    // This is a nested class to allow private access.
200
    class PlaybackStateHandler {
201
    public:
202
        PlaybackStateHandler(PlaybackManager& manager)
203
0
            : m_manager(manager)
204
0
        {
205
0
        }
206
0
        virtual ~PlaybackStateHandler() = default;
207
        virtual StringView name() = 0;
208
209
0
        virtual ErrorOr<void> on_enter() { return {}; }
210
211
0
        virtual ErrorOr<void> play() { return {}; }
212
        virtual bool is_playing() const = 0;
213
        virtual PlaybackState get_state() const = 0;
214
0
        virtual ErrorOr<void> pause() { return {}; }
215
0
        virtual ErrorOr<void> buffer() { return {}; }
216
        virtual ErrorOr<void> seek(Duration target_timestamp, SeekMode);
217
        virtual ErrorOr<void> stop();
218
219
        virtual Duration current_time() const;
220
221
0
        virtual ErrorOr<void> do_timed_state_update() { return {}; }
222
223
    protected:
224
        template<class T, class... Args>
225
        ErrorOr<void> replace_handler_and_delete_this(Args... args);
226
227
        PlaybackManager& manager() const;
228
229
        PlaybackManager& manager()
230
0
        {
231
0
            return const_cast<PlaybackManager&>(const_cast<PlaybackStateHandler const*>(this)->manager());
232
0
        }
233
234
    private:
235
        PlaybackManager& m_manager;
236
#if PLAYBACK_MANAGER_DEBUG
237
        bool m_has_exited { false };
238
#endif
239
    };
240
};
241
242
}