mirror of
https://github.com/samsonjs/media.git
synced 2026-03-30 10:15:48 +00:00
Notes: - The way this works is that every time the player needs to select some tracks it invokes the TrackSelector. When a track selection is actually activated (i.e. "hits the screen") it gets passed back to the TrackSelector, which allows it to expose the current tracks through an API that it may choose to define. Since playlist support doesn't exist yet, it's currently the case that the pass-back always occurs immediately. - A TrackSelector can invalidate its previous selections if its selection criteria changes. This will force the player to invoke it again to make a new selection. If the new selection is the same as the previous one for a renderer then the player handles this efficiently (i.e. turns it into a no-op). - DefaultTrackSelector supports disabling/enabling of renderers. Separately, it supports overrides to select specific formats. Since formats may change (playlists/periods), overrides are specific to not only the renderer but also the set of formats that are available to it. If the formats available to a renderer change then the override will no longer apply. If the same set of formats become available at some point later, it will apply once more. This will nicely handle cases like ad-insertion where the ads have different formats, but all segments of main content use the same set of formats. - In general, in multi-period or playlist cases, the preferred way of selecting formats will be via constraints (e.g. "don't play HD", "prefer higher quality audio") rather than explicit format selections. The ability to set various constraints on DefaultTrackSelector is future work. Note about the demo app: - I've removed the verbose log toggle. I doubt anyone has ever used it! I've also removed the background audio option. Without using a service it can't be considered a reference implementation, so it's probably best to leave developers to figure this one out. Finally, listening to AudioCapabilities has also gone. This will be replaced by having the player detect and handle the capabilities change internally in a future CL. This will work by allowing a renderer to invalidate the track selections when its capabilities change, much like how a selector is able to invalidate the track selections in this CL. - It's now possible to enable ABR with an arbitrary subset of tracks. - Unsupported tracks are shown grayed out in the UI. I'm not showing tracks that aren't associated to any renderer, but we could optionally add that later. - Every time the tracks change, there's logcat output showing all of the tracks and which ones are enabled. Unassociated tracks are displayed in this output. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117122202
198 lines
5.9 KiB
Java
198 lines
5.9 KiB
Java
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.google.android.exoplayer;
|
|
|
|
import com.google.android.exoplayer.util.Assertions;
|
|
|
|
import android.annotation.SuppressLint;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.util.Log;
|
|
|
|
import java.util.concurrent.CopyOnWriteArraySet;
|
|
|
|
/**
|
|
* Concrete implementation of {@link ExoPlayer}.
|
|
*/
|
|
/* package */ final class ExoPlayerImpl implements ExoPlayer {
|
|
|
|
private static final String TAG = "ExoPlayerImpl";
|
|
|
|
private final Handler eventHandler;
|
|
private final ExoPlayerImplInternal internalPlayer;
|
|
private final CopyOnWriteArraySet<Listener> listeners;
|
|
|
|
private boolean playWhenReady;
|
|
private int playbackState;
|
|
private int pendingPlayWhenReadyAcks;
|
|
|
|
/**
|
|
* Constructs an instance. Must be invoked from a thread that has an associated {@link Looper}.
|
|
*
|
|
* @param renderers The {@link TrackRenderer}s that will be used by the instance.
|
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
|
* @param minBufferMs A minimum duration of data that must be buffered for playback to start
|
|
* or resume following a user action such as a seek.
|
|
* @param minRebufferMs A minimum duration of data that must be buffered for playback to resume
|
|
* after a player invoked rebuffer (i.e. a rebuffer that occurs due to buffer depletion, and
|
|
* not due to a user action such as starting playback or seeking).
|
|
*/
|
|
@SuppressLint("HandlerLeak")
|
|
public ExoPlayerImpl(TrackRenderer[] renderers, TrackSelector trackSelector, int minBufferMs,
|
|
int minRebufferMs) {
|
|
Log.i(TAG, "Init " + ExoPlayerLibraryInfo.VERSION);
|
|
Assertions.checkNotNull(renderers);
|
|
Assertions.checkState(renderers.length > 0);
|
|
this.playWhenReady = false;
|
|
this.playbackState = STATE_IDLE;
|
|
this.listeners = new CopyOnWriteArraySet<>();
|
|
eventHandler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message msg) {
|
|
ExoPlayerImpl.this.handleEvent(msg);
|
|
}
|
|
};
|
|
internalPlayer = new ExoPlayerImplInternal(renderers, trackSelector, minBufferMs, minRebufferMs,
|
|
playWhenReady, eventHandler);
|
|
}
|
|
|
|
@Override
|
|
public Looper getPlaybackLooper() {
|
|
return internalPlayer.getPlaybackLooper();
|
|
}
|
|
|
|
@Override
|
|
public void addListener(Listener listener) {
|
|
listeners.add(listener);
|
|
}
|
|
|
|
@Override
|
|
public void removeListener(Listener listener) {
|
|
listeners.remove(listener);
|
|
}
|
|
|
|
@Override
|
|
public int getPlaybackState() {
|
|
return playbackState;
|
|
}
|
|
|
|
@Override
|
|
public void prepare(SampleSource source) {
|
|
internalPlayer.prepare(source);
|
|
}
|
|
|
|
@Override
|
|
public void setPlayWhenReady(boolean playWhenReady) {
|
|
if (this.playWhenReady != playWhenReady) {
|
|
this.playWhenReady = playWhenReady;
|
|
pendingPlayWhenReadyAcks++;
|
|
internalPlayer.setPlayWhenReady(playWhenReady);
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean getPlayWhenReady() {
|
|
return playWhenReady;
|
|
}
|
|
|
|
@Override
|
|
public boolean isPlayWhenReadyCommitted() {
|
|
return pendingPlayWhenReadyAcks == 0;
|
|
}
|
|
|
|
@Override
|
|
public void seekTo(long positionMs) {
|
|
internalPlayer.seekTo(positionMs);
|
|
}
|
|
|
|
@Override
|
|
public void stop() {
|
|
internalPlayer.stop();
|
|
}
|
|
|
|
@Override
|
|
public void release() {
|
|
internalPlayer.release();
|
|
eventHandler.removeCallbacksAndMessages(null);
|
|
}
|
|
|
|
@Override
|
|
public void sendMessage(ExoPlayerComponent target, int messageType, Object message) {
|
|
internalPlayer.sendMessage(target, messageType, message);
|
|
}
|
|
|
|
@Override
|
|
public void blockingSendMessage(ExoPlayerComponent target, int messageType, Object message) {
|
|
internalPlayer.blockingSendMessage(target, messageType, message);
|
|
}
|
|
|
|
@Override
|
|
public long getDuration() {
|
|
return internalPlayer.getDuration();
|
|
}
|
|
|
|
@Override
|
|
public long getCurrentPosition() {
|
|
return internalPlayer.getCurrentPosition();
|
|
}
|
|
|
|
@Override
|
|
public long getBufferedPosition() {
|
|
return internalPlayer.getBufferedPosition();
|
|
}
|
|
|
|
@Override
|
|
public int getBufferedPercentage() {
|
|
long bufferedPosition = getBufferedPosition();
|
|
long duration = getDuration();
|
|
return bufferedPosition == ExoPlayer.UNKNOWN_TIME || duration == ExoPlayer.UNKNOWN_TIME ? 0
|
|
: (int) (duration == 0 ? 100 : (bufferedPosition * 100) / duration);
|
|
}
|
|
|
|
// Not private so it can be called from an inner class without going through a thunk method.
|
|
/* package */ void handleEvent(Message msg) {
|
|
switch (msg.what) {
|
|
case ExoPlayerImplInternal.MSG_STATE_CHANGED: {
|
|
playbackState = msg.arg1;
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayerStateChanged(playWhenReady, playbackState);
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_SET_PLAY_WHEN_READY_ACK: {
|
|
pendingPlayWhenReadyAcks--;
|
|
if (pendingPlayWhenReadyAcks == 0) {
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayWhenReadyCommitted();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ExoPlayerImplInternal.MSG_ERROR: {
|
|
ExoPlaybackException exception = (ExoPlaybackException) msg.obj;
|
|
for (Listener listener : listeners) {
|
|
listener.onPlayerError(exception);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|