mirror of
https://github.com/jellyfin/jellyfin-desktop.git
synced 2026-10-26 16:45:24 +00:00
Expose mpv buffered ranges to the web player (#1166)
Observe demuxer-cache-state from mpv, extract seekable-ranges, and emit them as a QVariantList signal. JavaScript connects to the signal and stores the ranges for getBufferedRanges().
This commit is contained in:
1 parent
75d19da3a0
commit
ece1c034d5
4 files changed
+48
-2
No files matched your search
@@ -50,12 +50,14 @@ class mpvAudioPlayer {
|
||||
self._volume = self.getSavedVolume() * 100;
|
||||
self._playRate = 1;
|
||||
self._hasConnection = false;
|
||||
self._bufferedRanges = [];
|
||||
|
||||
self.play = (options) => {
|
||||
self._started = false;
|
||||
self._timeUpdated = false;
|
||||
self._currentTime = null;
|
||||
self._duration = undefined;
|
||||
self._bufferedRanges = [];
|
||||
|
||||
const player = window.api.player;
|
||||
if (!self._hasConnection) {
|
||||
@@ -66,6 +68,7 @@ class mpvAudioPlayer {
|
||||
player.updateDuration.connect(onDuration);
|
||||
player.error.connect(onError);
|
||||
player.paused.connect(onPause);
|
||||
player.bufferedRangesUpdated.connect(onBufferedRangesUpdated);
|
||||
}
|
||||
|
||||
return setCurrentSrc(options);
|
||||
@@ -139,6 +142,8 @@ class mpvAudioPlayer {
|
||||
player.updateDuration.disconnect(onDuration);
|
||||
player.error.disconnect(onError);
|
||||
player.paused.disconnect(onPause);
|
||||
player.bufferedRangesUpdated.disconnect(onBufferedRangesUpdated);
|
||||
self._bufferedRanges = [];
|
||||
};
|
||||
|
||||
function onDuration(duration) {
|
||||
@@ -180,6 +185,10 @@ class mpvAudioPlayer {
|
||||
self.events.trigger(self, 'pause');
|
||||
}
|
||||
|
||||
function onBufferedRangesUpdated(ranges) {
|
||||
self._bufferedRanges = ranges;
|
||||
}
|
||||
|
||||
function onError(error) {
|
||||
console.error(`media error: ${error}`);
|
||||
toast(`media error: ${error}`);
|
||||
@@ -240,7 +249,7 @@ class mpvAudioPlayer {
|
||||
}
|
||||
|
||||
getBufferedRanges() {
|
||||
return [];
|
||||
return this._bufferedRanges;
|
||||
}
|
||||
|
||||
pause() {
|
||||
|
||||
@@ -114,6 +114,17 @@
|
||||
* @type {boolean}
|
||||
*/
|
||||
this._hasConnection = false;
|
||||
/**
|
||||
* @type {Array<{start: number, end: number}>}
|
||||
*/
|
||||
this._bufferedRanges = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
this.onBufferedRangesUpdated = (ranges) => {
|
||||
this._bufferedRanges = ranges;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -234,6 +245,7 @@
|
||||
this._started = false;
|
||||
this._timeUpdated = false;
|
||||
this._currentTime = null;
|
||||
this._bufferedRanges = [];
|
||||
|
||||
this.resetSubtitleOffset();
|
||||
if (options.fullscreen) {
|
||||
@@ -481,6 +493,7 @@
|
||||
|
||||
const player = window.api.player;
|
||||
this._hasConnection = false;
|
||||
this._bufferedRanges = [];
|
||||
player.playing.disconnect(this.onPlaying);
|
||||
player.positionUpdate.disconnect(this.onTimeUpdate);
|
||||
player.finished.disconnect(this.onEnded);
|
||||
@@ -488,6 +501,7 @@
|
||||
player.updateDuration.disconnect(this.onDuration);
|
||||
player.error.disconnect(this.onError);
|
||||
player.paused.disconnect(this.onPause);
|
||||
player.bufferedRangesUpdated.disconnect(this.onBufferedRangesUpdated);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -535,6 +549,7 @@
|
||||
player.updateDuration.connect(this.onDuration);
|
||||
player.error.connect(this.onError);
|
||||
player.paused.connect(this.onPause);
|
||||
player.bufferedRangesUpdated.connect(this.onBufferedRangesUpdated);
|
||||
|
||||
// Log all other signals
|
||||
player.buffering.connect((percent) => {
|
||||
@@ -804,7 +819,7 @@
|
||||
}
|
||||
|
||||
getBufferedRanges() {
|
||||
return [];
|
||||
return this._bufferedRanges;
|
||||
}
|
||||
|
||||
getStats() {
|
||||
|
||||
@@ -186,6 +186,7 @@ void PlayerComponent::initializeMpv()
|
||||
mpv_observe_property(m_mpv->mpv(), 0, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(m_mpv->mpv(), 0, "audio-device-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(m_mpv->mpv(), 0, "video-dec-params", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(m_mpv->mpv(), 0, "demuxer-cache-state", MPV_FORMAT_NODE);
|
||||
|
||||
// Setup a hook with the ID 1, which is run during the file is loaded.
|
||||
// Used to delay playback start for display framerate switching.
|
||||
@@ -569,6 +570,23 @@ void PlayerComponent::handleMpvEvent(mpv_event *event)
|
||||
{
|
||||
updateAudioDeviceList();
|
||||
}
|
||||
else if (strcmp(prop->name, "demuxer-cache-state") == 0 && prop->format == MPV_FORMAT_NODE)
|
||||
{
|
||||
constexpr double ticksPerSecond = 10000000.0; // 100ns ticks (Jellyfin's internal unit)
|
||||
auto *node = static_cast<mpv_node*>(prop->data);
|
||||
QVariantMap cacheState = mpv::qt::node_to_variant(node).toMap();
|
||||
QVariantList seekableRanges = cacheState[QStringLiteral("seekable-ranges")].toList();
|
||||
QVariantList ranges;
|
||||
for (const QVariant &entry : seekableRanges)
|
||||
{
|
||||
QVariantMap rangeMap = entry.toMap();
|
||||
QVariantMap r;
|
||||
r[QStringLiteral("start")] = static_cast<qint64>(rangeMap[QStringLiteral("start")].toDouble() * ticksPerSecond);
|
||||
r[QStringLiteral("end")] = static_cast<qint64>(rangeMap[QStringLiteral("end")].toDouble() * ticksPerSecond);
|
||||
ranges.append(r);
|
||||
}
|
||||
emit bufferedRangesUpdated(ranges);
|
||||
}
|
||||
else if (strcmp(prop->name, "video-dec-params") == 0)
|
||||
{
|
||||
// Aspect might be known now (or it changed during playback), so update settings
|
||||
|
||||
@@ -219,6 +219,10 @@ Q_SIGNALS:
|
||||
void seekPerformed(qint64 positionMs);
|
||||
void metadataChanged(const QVariantMap& metadata);
|
||||
void volumeChanged(double volume);
|
||||
|
||||
// Buffered ranges from demuxer-cache-state, as list of {start, end} in ticks
|
||||
void bufferedRangesUpdated(const QVariantList& ranges);
|
||||
|
||||
private:
|
||||
// this is the function actually implemented in the backends. the variantmap contains
|
||||
// a few known keys:
|
||||
|
||||
Reference in new issue
Block a user