LibMedia+LibWeb: End media element playback based on the pipeline EOS
Instead of comparing the current time to the duration, the playback manager now has an explicit Ended state that jumps to the duration. The element simply reacts to that to trigger the ended event and attribute, along with all the other steps involved. This moves the ended event to fire after the seeked event, which matches other browsers' behavior. The spec doesn't explicitly say which order they should fire in.
This commit is contained in:
parent
af9ab3b3fa
commit
9e2a820884
9 changed files with 88 additions and 10 deletions
|
|
@ -7,11 +7,17 @@
|
|||
#include "BufferingStateHandler.h"
|
||||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
#include <LibMedia/PlaybackStates/EndedStateHandler.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
void BufferingStateHandler::on_pipeline_status_changed(PipelineStatus status)
|
||||
{
|
||||
if (status == PipelineStatus::EndOfStream) {
|
||||
manager().replace_state_handler<EndedStateHandler>();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status != PipelineStatus::Blocked)
|
||||
resume();
|
||||
}
|
||||
|
|
|
|||
51
Libraries/LibMedia/PlaybackStates/EndedStateHandler.h
Normal file
51
Libraries/LibMedia/PlaybackStates/EndedStateHandler.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
|
||||
namespace Media {
|
||||
|
||||
class EndedStateHandler final : public PlaybackStateHandler {
|
||||
public:
|
||||
EndedStateHandler(PlaybackManager& manager)
|
||||
: PlaybackStateHandler(manager)
|
||||
{
|
||||
}
|
||||
virtual ~EndedStateHandler() override = default;
|
||||
|
||||
virtual void on_enter() override
|
||||
{
|
||||
manager().m_time_provider->pause();
|
||||
}
|
||||
virtual void on_exit() override { }
|
||||
|
||||
virtual AK::Duration current_time() const override
|
||||
{
|
||||
return manager().duration();
|
||||
}
|
||||
|
||||
virtual void play() override { }
|
||||
virtual void pause() override { }
|
||||
|
||||
virtual bool is_playing() override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual PlaybackState state() override
|
||||
{
|
||||
return PlaybackState::Ended;
|
||||
}
|
||||
virtual AvailableData available_data() override
|
||||
{
|
||||
return AvailableData::Current;
|
||||
}
|
||||
|
||||
virtual void on_pipeline_status_changed(PipelineStatus) override { }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@
|
|||
X(PlayingStateHandler) \
|
||||
X(PausedStateHandler) \
|
||||
X(ResumingStateHandler) \
|
||||
X(SeekingStateHandler)
|
||||
X(SeekingStateHandler) \
|
||||
X(EndedStateHandler)
|
||||
|
||||
namespace Media {
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ enum class PlaybackState : u8 {
|
|||
Playing,
|
||||
Paused,
|
||||
Seeking,
|
||||
Ended,
|
||||
};
|
||||
|
||||
constexpr StringView playback_state_to_string(PlaybackState state)
|
||||
|
|
@ -32,6 +33,8 @@ constexpr StringView playback_state_to_string(PlaybackState state)
|
|||
return "Paused"sv;
|
||||
case PlaybackState::Seeking:
|
||||
return "Seeking"sv;
|
||||
case PlaybackState::Ended:
|
||||
return "Ended"sv;
|
||||
}
|
||||
return "Invalid"sv;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
#include <LibMedia/PlaybackStates/EndedStateHandler.h>
|
||||
#include <LibMedia/PlaybackStates/SeekingStateHandler.h>
|
||||
|
||||
#include "PlaybackStateHandler.h"
|
||||
|
|
@ -23,7 +24,8 @@ void PlaybackStateHandler::seek(AK::Duration timestamp, SeekMode mode)
|
|||
|
||||
void PlaybackStateHandler::on_pipeline_status_changed(PipelineStatus status)
|
||||
{
|
||||
(void)status;
|
||||
if (status == PipelineStatus::EndOfStream)
|
||||
manager().replace_state_handler<EndedStateHandler>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <LibMedia/PlaybackManager.h>
|
||||
#include <LibMedia/PlaybackStates/BufferingStateHandler.h>
|
||||
#include <LibMedia/PlaybackStates/EndedStateHandler.h>
|
||||
#include <LibMedia/PlaybackStates/PausedStateHandler.h>
|
||||
|
||||
namespace Media {
|
||||
|
|
@ -19,8 +20,13 @@ void PlayingStateHandler::pause()
|
|||
|
||||
void PlayingStateHandler::on_pipeline_status_changed(PipelineStatus status)
|
||||
{
|
||||
if (status == PipelineStatus::Blocked)
|
||||
if (status == PipelineStatus::Blocked) {
|
||||
manager().replace_state_handler<BufferingStateHandler>(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == PipelineStatus::EndOfStream)
|
||||
manager().replace_state_handler<EndedStateHandler>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,6 +60,11 @@ public:
|
|||
if (!resolves_seek(status))
|
||||
return;
|
||||
|
||||
if (status == PipelineStatus::EndOfStream) {
|
||||
PlaybackStateHandler::on_pipeline_status_changed(status);
|
||||
return;
|
||||
}
|
||||
|
||||
resume();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -459,11 +459,6 @@ void HTMLMediaElement::set_current_playback_position(double playback_position)
|
|||
|
||||
time_marches_on();
|
||||
|
||||
// NOTE: Invoking the following steps is not listed in the spec. Rather, the spec just describes the scenario in
|
||||
// which these steps should be invoked, which is when we've reached the end of the media playback.
|
||||
if (m_current_playback_position == m_duration)
|
||||
reached_end_of_media_playback();
|
||||
|
||||
upon_has_ended_playback_possibly_changed();
|
||||
update_natural_dimensions();
|
||||
|
||||
|
|
@ -2043,6 +2038,8 @@ void HTMLMediaElement::forget_media_resource_specific_tracks()
|
|||
// of text tracks all the media-resource-specific text tracks, then empty the media element's audioTracks attribute's AudioTrackList object, then
|
||||
// empty the media element's videoTracks attribute's VideoTrackList object. No events (in particular, no removetrack events) are fired as part of
|
||||
// this; the error and emptied events, fired by the algorithms that invoke this one, can be used instead.
|
||||
if (m_playback_manager)
|
||||
m_playback_manager->on_playback_state_change = nullptr;
|
||||
m_audio_tracks->remove_all_tracks();
|
||||
m_video_tracks->remove_all_tracks();
|
||||
m_playback_manager.clear();
|
||||
|
|
@ -2269,6 +2266,10 @@ void HTMLMediaElement::on_playback_manager_state_change()
|
|||
auto state = m_playback_manager->state();
|
||||
if (seeking() && state != Media::PlaybackState::Seeking)
|
||||
finish_seeking_element();
|
||||
if (state == Media::PlaybackState::Ended && !m_error) {
|
||||
set_current_playback_position(m_duration);
|
||||
reached_end_of_media_playback();
|
||||
}
|
||||
|
||||
// NB: Queue the readyState update as a task so that it will never run before the durationchange and loadedmetadata
|
||||
// events are fired. This ensures that readyState has a deterministic value in those events.
|
||||
|
|
@ -2699,10 +2700,13 @@ bool HTMLMediaElement::has_ended_playback() const
|
|||
if (m_ready_state < ReadyState::HaveMetadata)
|
||||
return false;
|
||||
|
||||
VERIFY(m_playback_manager != nullptr);
|
||||
// Either:
|
||||
if (
|
||||
// The current playback position is the end of the media resource, and
|
||||
m_current_playback_position == m_duration &&
|
||||
// NB: This is represented by the playback manager's Ended state, which is only entered once the pipeline has
|
||||
// consumed all real media data.
|
||||
m_playback_manager->state() == Media::PlaybackState::Ended &&
|
||||
|
||||
// The direction of playback is forwards, and
|
||||
direction_of_playback() == PlaybackDirection::Forwards &&
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ resize: videoWidth=320 videoHeight=240
|
|||
seeked: videoWidth=320 videoHeight=240
|
||||
--- seek to end ---
|
||||
resize: videoWidth=640 videoHeight=480
|
||||
ended: videoWidth=640 videoHeight=480
|
||||
seeked: videoWidth=640 videoHeight=480
|
||||
ended: videoWidth=640 videoHeight=480
|
||||
|
|
|
|||
Loading…
Reference in a new issue