muxinc / media-chrome

Custom elements (web components) for making audio and video player controls that look great in your website or app.
https://media-chrome.org
MIT License
1.11k stars 56 forks source link

Add seekToLiveOffset attribute #923

Open k-g-a opened 2 weeks ago

k-g-a commented 2 weeks ago

The problem

Currentrly, if we have a paused live stream and issue MEDIA_PLAY_REQUEST (i.e. via pressing play button), we are thrown straight to the end of seekable range:

const liveEdgeTime = stateMediator.mediaSeekable.get(stateOwners)?.[1];

if (notDvr && liveEdgeTime) {
  stateMediator.mediaCurrentTime.set(liveEdgeTime, stateOwners);
}

Unfortunatelly, this often causes a live stream to stall afterwards (not necessary immediately) due to the fact that it's playing too close to the edge - new video fragments just don't catch up in time, especially if poor network conditions are in place.

Proposed solution

The first thing that comes to mind is to use liveEdgeOffset within MEDIA_PLAY_REQUEST handler:

const liveEdgeTime = stateMediator.mediaSeekable.get(stateOwners)?.[1];

if (notDvr && liveEdgeTime) {
  const liveEdgeOffset = stateOwners.options.liveEdgeOffset ?? 0;
  stateMediator.mediaCurrentTime.set(liveEdgeTime - liveEdgeOffset, stateOwners);
}

But this may lead up to the other issue: if we are close enough to the moment of the next video segment arrival, as soon as it arrives we will fall back behind the liveEdgeOffset (i.e. duration/seekableEnd jump forward by the amount of the segment length). This will cause media-live-button to show that we are "not live" (i.e. become gray), but in a few moments the playback will catch up with liveEdgeOffset so the media-live-button becomes "live" (i.e. red). This process will continue to repeat and the media-live-button will "flicker". This is especially the case for segments with small durations (i.e. 3-5 seconds).

So, it's more reasonable to introduce another attribute called SEEK_TO_LIVE_OFFSET: 'seektoliveoffset' (with default of +this.getAttribute(Attributes.LIVE_EDGE_OFFSET) for example) and use it within MEDIA_PLAY_REQUEST nahdler.

In this case we could provide, for example of a 3-seconds video fragment length, the following values <media-controller liveedgeoffset="15" seektoliveoffset="11"> and get both: reliable media-live-button indication and enough segments ahead to overcome network problems.

In case this proposal is accepted, I can make a PR.

P.S.

We used to "hack" this behaviour in previous versions of media-chrome in the following way:


// in a react component wrapper
private refMediaController = (element: EnhancedMediaControllerWebComponent | null): void => {
    if (this.mediaControllerRef === element) {
      return;
    }

    if (this.mediaControllerRef) {
      this.mediaControllerRef.removeEventListener(
        MediaUIEvents.MEDIA_SEEK_TO_LIVE_REQUEST,
        this._handleMediaSeekToLiveRequestWithOffset
      );
    }

    if (element) {
      if (element._handleMediaSeekToLiveRequest) {
        element.removeEventListener(MediaUIEvents.MEDIA_SEEK_TO_LIVE_REQUEST, element._handleMediaSeekToLiveRequest);
      }
      element.addEventListener(MediaUIEvents.MEDIA_SEEK_TO_LIVE_REQUEST, this._handleMediaSeekToLiveRequestWithOffset);
    }

    this.mediaControllerRef = element;
  };

  private _handleMediaSeekToLiveRequestWithOffset = (): void => {
    const mediaControllerRef = this.mediaControllerRef;

    // some checks are omited for the sake of readability

    const endTime = seekable.end(seekable.length - 1);
    const seekToLiveOffset = this.props.seekToLiveOffset || 0;
    media.currentTime = endTime - seekToLiveOffset;
  };
k-g-a commented 2 weeks ago

Just found that there was (and still is, but unused) the NO_AUTO_SEEK_TO_LIVE: 'noautoseektolive' attribute.

It feels like it used to work, is it a regression? If so, I can file another issue about it.