Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow the app to override the start position when playback transitions to another source #2403

Closed
Jogan opened this issue Jan 31, 2017 · 25 comments

Comments

@Jogan
Copy link

Jogan commented Jan 31, 2017

I have two sources in a concatenating source and would like to seek to a specific position once the first source has finished playing. Should I make use of onTracksChanged()? or is there a better solution?

@andrewlewis
Copy link
Collaborator

Wrapping the second source in a ClippingMediaSource should work for this. See #1988 for more info.

@Jogan
Copy link
Author

Jogan commented Feb 1, 2017 via email

@andrewlewis andrewlewis changed the title How to seek to position for second source in ConcatenatingMediaSource after first source has played? Allow the app to override the start position when playback transitions to another source Feb 1, 2017
@andrewlewis
Copy link
Collaborator

We have a way to do this with Timeline.getDefaultPositionUs but it's not convenient to use at the moment unless you have a custom MediaSource where you can update the Timeline when the desired start position changes.

I'll mark this as an enhancement to track providing an easy way for apps to set the start position, perhaps via ExtractorMediaSource or ConcatenatingMediaSource.

@Jogan
Copy link
Author

Jogan commented Feb 1, 2017

Thank you. For now I've just separated out the sources from the ConcatenatingMediaSource.

@ojw28
Copy link
Contributor

ojw28 commented Sep 20, 2017

You can do this fairly easily by wrapping your second source in a class similar to the one below. So you'd have:

ConcatenatingMediaSource(
    FirstMediaSource
    CustomStartPositionSource(SecondMediaSource, desiredStartPositionMs)
)

Class:

public class CustomStartPositionSource implements MediaSource {

  private final MediaSource wrappedSource;
  private final long startPositionUs;

  public CustomStartPositionSource(MediaSource wrappedSource, long startPositionMs) {
    this.wrappedSource = wrappedSource;
    this.startPositionUs = C.msToUs(startPositionMs);
  }

  @Override
  public void prepareSource(ExoPlayer player, boolean isTopLevelSource, final Listener listener) {
    wrappedSource.prepareSource(player, isTopLevelSource, new Listener() {
      @Override
      public void onSourceInfoRefreshed(Timeline timeline, @Nullable Object manifest) {
        listener.onSourceInfoRefreshed(
            new CustomStartPositionTimeline(timeline, startPositionUs), manifest);
      }
    });
  }

  @Override
  public void maybeThrowSourceInfoRefreshError() throws IOException {
    wrappedSource.maybeThrowSourceInfoRefreshError();
  }

  @Override
  public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator) {
    return wrappedSource.createPeriod(id, allocator);
  }

  @Override
  public void releasePeriod(MediaPeriod mediaPeriod) {
    wrappedSource.releasePeriod(mediaPeriod);
  }

  @Override
  public void releaseSource() {
    wrappedSource.releaseSource();
  }

  private static final class CustomStartPositionTimeline extends ForwardingTimeline {

    private final long defaultPositionUs;

    public CustomStartPositionTimeline(Timeline wrapped, long defaultPositionUs) {
      super(wrapped);
      this.defaultPositionUs = defaultPositionUs;
    }

    @Override
    public Window getWindow(int windowIndex, Window window, boolean setIds,
        long defaultPositionProjectionUs) {
      super.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
      window.defaultPositionUs = defaultPositionUs;
      return window;
    }

  }

}

It's unclear to me whether this feature is really important enough to make it into the library proper. What's the use case? Do you think other people are likely to need it?

@Jogan
Copy link
Author

Jogan commented Oct 30, 2017

@ojw28 the solution posted above works great. The use case was pretty specific I'm not sure how many other people might need this in the library itself. Basically, we were displaying a preroll video (not an ad) every time a user started content. This pre-roll was followed by the content they may or may not had already started watching. We wanted them to be able to seek back from the initial start position without seeing the pre-roll again.

Closing since the solution above works for our case.

@Jogan Jogan closed this as completed Oct 30, 2017
@noamtamim
Copy link
Contributor

Hi @ojw28
I haven't tested the above solution yet (I will soon when I get the chance). But our use case is that the last watched position is stored on the server and the user wants to start from that place (similar to Netflix "Continue Watching"). Regular prepare and then seek means you waste time and bandwidth loading the first few chunks even though you don't intend to play them. Ideally ExoPlayer will only load the relevant chunks.
Another use case is for live with DVR. When we start playing live+DVR content ExoPlayer begins from the DVR buffer (like -15 minutes) instead of the live edge. We then have to seek to the live edge and again, this takes time.

@andrewlewis
Copy link
Collaborator

@noamtamim For the first case, can you just seek before preparing the player?

@giladna
Copy link

giladna commented Nov 23, 2017

@andrewlewis what is the impact of seeking player instance to some position if no mediaSource was set?

@ojw28
Copy link
Contributor

ojw28 commented Nov 23, 2017

Provided you set resetPosition to false when subsequently calling prepare on the player, playback will start from the position that you performed the seek to. Seeking before prepare is normally more efficient than seeking after prepare. If you look in the ExoPlayer demo app, you'll see that we seek before prepare in the case that we have a resume position.

@ojw28
Copy link
Contributor

ojw28 commented Nov 23, 2017

The obvious caveat of seeking before prepare is that you need to know that where you're seeking to is actually going to be valid in the source you're going to prepare with. Although in many cases you do know this (e.g. if it's a resume position into a piece of content, or if you know the content duration already, etc). Nothing particularly bad will happen if you seek outside of the source either. If it turns out you've requested a seek that's beyond the end of the source, I think the player will just transition to the ended state.

@noamtamim
Copy link
Contributor

Thanks @ojw28 and @andrewlewis, I didn't even know it's possible to seek before prepare.

@giladna
Copy link

giladna commented Nov 23, 2017

@ojw28
Regarding the demo app...
In latest dev code we could see it was removed :(
what are the plans regarding it?

10x

@ojw28
Copy link
Contributor

ojw28 commented Nov 23, 2017

What was removed? The demo app is still there. It's just moved:
https://github.com/google/ExoPlayer/tree/dev-v2/demos/main

@giladna
Copy link

giladna commented Nov 23, 2017

let me recheck might be studio issue, it was not shown when I switched to it I could see now in git that it is called demos now

@giladna
Copy link

giladna commented Nov 23, 2017

@ojw28
In my Studio 3.0 if I switch from older version to v2.6.0 it is not there after reopen the project inly demo-ima is there

@giladna
Copy link

giladna commented Nov 23, 2017

include modulePrefix + 'demo'
include modulePrefix + 'demo-ima'
include modulePrefix + 'playbacktests'
project(modulePrefix + 'demo').projectDir = new File(rootDir, 'demos/main')
project(modulePrefix + 'demo-ima').projectDir = new File(rootDir, 'demos/ima')
project(modulePrefix + 'playbacktests').projectDir = new File(rootDir, 'playbacktests')

@giladna
Copy link

giladna commented Nov 23, 2017

@ojw28
keeping the same name makes studio to confusion
after changing it to:
include modulePrefix + 'demos'
project(modulePrefix + 'demos').projectDir = new File(rootDir, 'demos/main')

it appeared

@ojw28
Copy link
Contributor

ojw28 commented Nov 23, 2017

Sounds like a bug you should report to Android Studio (by the way, once you've made it appear, you can almost certainly revert your change again and have it remain visible).

@giladna
Copy link

giladna commented Nov 23, 2017

@ojw28 , @andrewlewis
Regarding seekTO...
in Sample
there is meaning to this action only if resumeWindow != UNSET
what is value that should be given if I just start from last know position from fresh player
does it mean that we also have to save also the resumeWindow and not only the lastPosition in some database to use this approach?

 boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
    if (haveResumePosition) {
      player.seekTo(resumeWindow, resumePosition);
    }
    player.prepare(mediaSource, !haveResumePosition, false);

@ojw28
Copy link
Contributor

ojw28 commented Nov 23, 2017

It's the app's responsibility to save and restore positions, if it wishes to do so.

@giladna
Copy link

giladna commented Nov 23, 2017

@ojw28
the point here that now app may save two values for this purpose and not only currentPosition
the window cannot be calculated internally?

@ojw28
Copy link
Contributor

ojw28 commented Nov 23, 2017

I don't understand what that means, but please just look at what the demo app does, since it clearly demonstrates this feature. Thanks.

@andrewlewis
Copy link
Collaborator

@giladna You can omit the resume window and just do player.seekTo(resumePosition) if the content you're playing has only one window (you only need it for multi-window content like playlists, where it's necessary to specify which item in the playlist to resume playing).

@giladna
Copy link

giladna commented Nov 26, 2017

Thanks :)

@google google locked and limited conversation to collaborators Mar 7, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants