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

When to call setSelectionOverride? #2343

Closed
DavidMihola opened this issue Jan 18, 2017 · 5 comments
Closed

When to call setSelectionOverride? #2343

DavidMihola opened this issue Jan 18, 2017 · 5 comments
Labels

Comments

@DavidMihola
Copy link

DavidMihola commented Jan 18, 2017

We are in the process of moving our app(s) to ExoPlayer 2 and I need to get a grip on how track selection works exactly (for HLS streams).

From looking at your demo app I learned to use DefaultTrackSelector.setSelectionOverride() - however, I have few questions:

  • Is this the only/clearly preferred way of tuning the track selection (for HLS streams) or is there a way to "front load" the process? (With ExoPlayer 1, for example, we had our own HLSRendererBuilder where we could reverse the list of Variants before the Renderer was even built).

  • Is ExoPlayer.EventListener.onTracksChanged() a good place to call setSelectionOverride()? I wasn't able to find any earlier place where I already had access to the tracks...

  • Is it correct/safe to call DefaultTrackSelector.getCurrentMappedTrackInfo() inside of onTracksChanged()? It seems to be because the tracks are set with trackSelector.onSelectionActivated(trackInfo.info) before calling onTracksChanged in ExoPlayerImpl, but I'd like be sure that there are no possible race conditions or other problems when doing something like this:

@Override
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
    Timber.d("onTracksChanged: %s\n%s", trackGroups, trackSelections);

    int indexOfVideoRenderer = -1;
    for (int i = 0; i < simpleExoPlayer.getRendererCount(); i++) {
        if (simpleExoPlayer.getRendererType(i) == C.TRACK_TYPE_VIDEO) {
            indexOfVideoRenderer = i;
        }
    }

    TrackGroupArray trackGroupArray = trackSelector.getCurrentMappedTrackInfo().getTrackGroups(indexOfVideoRenderer);

    Timber.d("index of video renderer = %d", indexOfVideoRenderer);
    for (int groupIndex = 0; groupIndex < trackGroupArray.length; groupIndex++) {
        Timber.d("TrackGroup %d", groupIndex);
        for (int trackIndex = 0; trackIndex < trackGroupArray.get(groupIndex).length; trackIndex++) {
            Timber.d("\tTrack %d: %s (supported by video renderer = %s)", trackIndex, trackGroupArray.get(groupIndex).getFormat(trackIndex), trackSelector.getCurrentMappedTrackInfo().getTrackFormatSupport(indexOfVideoRenderer, groupIndex, trackIndex));
        }
    }

    trackSelector.setSelectionOverride(
            indexOfVideoRenderer,
            trackGroupArray,
            new MappingTrackSelector.SelectionOverride(
                    new FixedTrackSelection.Factory(),
                    0,
                    0
            )
    );
}

I seemed to need trackSelector.getCurrentMappedTrackInfo() to check which groups/tracks are actual video tracks - is there a better way to build the SelectionOverride directly from the TrackGroupArray and TrackSelectionArray that are passed to onTracksChanged()?

trackSelector.setSelectionOverride(
    indexOfVideoRenderer,
    trackGroupArray,
    new MappingTrackSelector.SelectionOverride(
        new AdaptiveVideoTrackSelection.Factory(bandwidthMeter),
        0,
        trackGroupArray.get(0).length - 1, 0
    )
);

But order of the track indices didn't seem to make a difference because of the InitializationTrackSelection in HlsChunkSource.

Sorry for my long and rambling post, but I hope my questions ultimately make sense!

@ojw28 ojw28 added the question label Jan 18, 2017
@ojw28
Copy link
Contributor

ojw28 commented Jan 18, 2017

Is this the only/clearly preferred way of tuning the track selection (for HLS streams) or is there a way to "front load" the process? (With ExoPlayer 1, for example, we had our own HLSRendererBuilder where we could reverse the list of Variants before the Renderer was even built).

It's helpful to distinguish between synchronous and asynchronous track selection. By synchronous I mean the tracks are selected on the playback thread prior to media being buffered. By asynchronous I mean a message is passed to the application thread and a selection is then passed back to the playback thread. With asynchronous selection there's a small "gap" during which the wrong selection is being buffered, and so it's less efficient at the start of playback. For selection during playback (e.g. as a result of user interaction) asynchronous selection is fine.

Your HlsRendererBuilder approach was synchronous. The asynchronous API in V1 was ExoPlayer.setSelectedTrack. In V2 setSelectionOverride is asynchronous. The synchronous API is TrackSelector.selectTracks, which is invoked directly on the playback thread. So you should customize what happens when TrackSelector.selectTracks is invoked to recreate what you had previously. It's a really complicated API to implement, and I doubt you'd want to implement it directly. Instead, it's probably sufficient for you to extend DefaultTrackSelector and override selectVideoTrack (and possibly selectAudioTrack). Alternatively, if you only need to specify constraints like a maximum video dimension, you can use the built in parameter functionality of DefaultTrackSelector. Do this prior to playback as below. The constraints you specify will then be applied during selection.

trackSelector.setParameters(trackSelector.getParameters()
    .withXXX()
    .withYYY());

Is ExoPlayer.EventListener.onTracksChanged() a good place to call setSelectionOverride()? I wasn't able to find any earlier place where I already had access to the tracks...

As above, it's preferable to use setSelectionOverride for changing tracks during playback only. Even during playback, it's still preferable to change tracks by replacing the DefaultTrackSelector parameters if this is sufficient for your needs.

Is it correct/safe to call DefaultTrackSelector.getCurrentMappedTrackInfo() inside of onTracksChanged()?

Yes.

And finally, I know that this has been discussed in other issues like #281 and more recently in #1848 but is there now a preferred way to force ExoPlayer to start with a particular track in an HLS stream?

It's easy to do this for DASH. I don't think it's possible to do this with HLS at this time. It's complicated in the HLS case by the fact we need to fetch a media chunk to determine what the tracks are.

@DavidMihola
Copy link
Author

OK, we'll see how far we get with Parameters first and try extending DefaultTrackSelector if we need to!

Thank you so much for your explanations!

@DavidMihola
Copy link
Author

Oh, one additional question if you don't mind...

Does the following make sense - I mean from the viewpoint of ExoPlayer architecture, and assuming you know the structure of the HLS playlists you are playing:

public final class FixedTrackSelectionLastFactory implements TrackSelection.Factory {

    private final int reason;
    private final Object data;

    public FixedTrackSelectionLastFactory() {
        this.reason = C.SELECTION_REASON_UNKNOWN;
        this.data = null;
    }

    /**
     * @param reason A reason for the track selection.
     * @param data   Optional data associated with the track selection.
     */
    public FixedTrackSelectionLastFactory(int reason, Object data) {
        this.reason = reason;
        this.data = data;
    }

    @Override
    public FixedTrackSelection createTrackSelection(TrackGroup group, int... tracks) {
        Assertions.checkArgument(tracks.length >= 1);
        return new FixedTrackSelection(group, tracks[tracks.length - 1], reason, data);
    }
}

and

trackSelector = new DefaultTrackSelector(new FixedTrackSelectionLastFactory());

Except for the fact that HLSChunkSource still starts with the first track because of InitializationTrackSelection it seems to work...

@ojw28
Copy link
Contributor

ojw28 commented Jan 20, 2017

The factory (adaptiveVideoTrackSelectionFactory) passed to the DefaultTrackSelector constructor is really intended to build an adaptive track selection containing all of the tracks provided to it, not select only one of them. If you want a fixed track selection you should probably pass null as the argument, which will force DefaultTrackSelector to make the "best" fixed selection whilst respecting the parameter constraints that you've set. Currently "best" means "highest pixel count". We should probably look at bitrate if the video resolution is unknown. If the behaviour isn't what you're looking for then you could override DefaultTrackSelector.selectFixedVideoTrack to do something different.

Note 1: What you're doing will probably work fine, it's just not doing things in the way the API was intended to be used. It's also quite fragile to rely on the ordering of the tracks. I don't think we guarantee anywhere to preserve the ordering (although I can't think of a reason why we'd be likely to change it).

Note 2: If you're making a track selection that doesn't contain the first track at all, and still see the first track being played out before adapting to your selection, I'd consider that a bug.

@ojw28
Copy link
Contributor

ojw28 commented Jan 20, 2017

I've filed #2353 to track the final note above. We'll merge a change to use bitrate as a tie-breaker for fixed track selection early next week.

ojw28 added a commit that referenced this issue Jan 24, 2017
If we don't have resolutions (and therefore cannot determine
pixel counts) then use bitrate as a tie breaker instead. Also
use pixel count as a tie breaker if pixel counts are known
but equal. Streams with known pixel counts will always be
preferred over streams without.

Issue: #2343

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=145269968
@google google locked and limited conversation to collaborators Jun 28, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants