Search in sources :

Example 1 with Listener

use of androidx.media3.common.Player.Listener in project media by androidx.

the class CastPlayer method updateInternalStateAndNotifyIfChanged.

// Internal methods.
// Call deprecated callbacks.
@SuppressWarnings("deprecation")
private void updateInternalStateAndNotifyIfChanged() {
    if (remoteMediaClient == null) {
        // There is no session. We leave the state of the player as it is now.
        return;
    }
    int oldWindowIndex = this.currentWindowIndex;
    @Nullable Object oldPeriodUid = !getCurrentTimeline().isEmpty() ? getCurrentTimeline().getPeriod(oldWindowIndex, period, /* setIds= */
    true).uid : null;
    updatePlayerStateAndNotifyIfChanged(/* resultCallback= */
    null);
    updateRepeatModeAndNotifyIfChanged(/* resultCallback= */
    null);
    updatePlaybackRateAndNotifyIfChanged(/* resultCallback= */
    null);
    boolean playingPeriodChangedByTimelineChange = updateTimelineAndNotifyIfChanged();
    Timeline currentTimeline = getCurrentTimeline();
    currentWindowIndex = fetchCurrentWindowIndex(remoteMediaClient, currentTimeline);
    @Nullable Object currentPeriodUid = !currentTimeline.isEmpty() ? currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */
    true).uid : null;
    if (!playingPeriodChangedByTimelineChange && !Util.areEqual(oldPeriodUid, currentPeriodUid) && pendingSeekCount == 0) {
        // Report discontinuity and media item auto transition.
        currentTimeline.getPeriod(oldWindowIndex, period, /* setIds= */
        true);
        currentTimeline.getWindow(oldWindowIndex, window);
        long windowDurationMs = window.getDurationMs();
        PositionInfo oldPosition = new PositionInfo(window.uid, period.windowIndex, window.mediaItem, period.uid, period.windowIndex, /* positionMs= */
        windowDurationMs, /* contentPositionMs= */
        windowDurationMs, /* adGroupIndex= */
        C.INDEX_UNSET, /* adIndexInAdGroup= */
        C.INDEX_UNSET);
        currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */
        true);
        currentTimeline.getWindow(currentWindowIndex, window);
        PositionInfo newPosition = new PositionInfo(window.uid, period.windowIndex, window.mediaItem, period.uid, period.windowIndex, /* positionMs= */
        window.getDefaultPositionMs(), /* contentPositionMs= */
        window.getDefaultPositionMs(), /* adGroupIndex= */
        C.INDEX_UNSET, /* adIndexInAdGroup= */
        C.INDEX_UNSET);
        listeners.queueEvent(Player.EVENT_POSITION_DISCONTINUITY, listener -> {
            listener.onPositionDiscontinuity(DISCONTINUITY_REASON_AUTO_TRANSITION);
            listener.onPositionDiscontinuity(oldPosition, newPosition, DISCONTINUITY_REASON_AUTO_TRANSITION);
        });
        listeners.queueEvent(Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(getCurrentMediaItem(), MEDIA_ITEM_TRANSITION_REASON_AUTO));
    }
    if (updateTracksAndSelectionsAndNotifyIfChanged()) {
        listeners.queueEvent(Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
        listeners.queueEvent(Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo));
    }
    updateAvailableCommandsAndNotifyIfChanged();
    listeners.flushEvents();
}
Also used : Timeline(androidx.media3.common.Timeline) Nullable(androidx.annotation.Nullable)

Example 2 with Listener

use of androidx.media3.common.Player.Listener in project media by androidx.

the class CastPlayer method seekTo.

// We still call Listener#onSeekProcessed() for backwards compatibility with listeners that
// don't implement onPositionDiscontinuity().
@SuppressWarnings("deprecation")
@Override
public void seekTo(int mediaItemIndex, long positionMs) {
    MediaStatus mediaStatus = getMediaStatus();
    // We assume the default position is 0. There is no support for seeking to the default position
    // in RemoteMediaClient.
    positionMs = positionMs != C.TIME_UNSET ? positionMs : 0;
    if (mediaStatus != null) {
        if (getCurrentMediaItemIndex() != mediaItemIndex) {
            remoteMediaClient.queueJumpToItem((int) currentTimeline.getPeriod(mediaItemIndex, period).uid, positionMs, null).setResultCallback(seekResultCallback);
        } else {
            remoteMediaClient.seek(positionMs).setResultCallback(seekResultCallback);
        }
        PositionInfo oldPosition = getCurrentPositionInfo();
        pendingSeekCount++;
        pendingSeekWindowIndex = mediaItemIndex;
        pendingSeekPositionMs = positionMs;
        PositionInfo newPosition = getCurrentPositionInfo();
        listeners.queueEvent(Player.EVENT_POSITION_DISCONTINUITY, listener -> {
            listener.onPositionDiscontinuity(DISCONTINUITY_REASON_SEEK);
            listener.onPositionDiscontinuity(oldPosition, newPosition, DISCONTINUITY_REASON_SEEK);
        });
        if (oldPosition.mediaItemIndex != newPosition.mediaItemIndex) {
            // TODO(internal b/182261884): queue `onMediaItemTransition` event when the media item is
            // repeated.
            MediaItem mediaItem = getCurrentTimeline().getWindow(mediaItemIndex, window).mediaItem;
            listeners.queueEvent(Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(mediaItem, MEDIA_ITEM_TRANSITION_REASON_SEEK));
        }
        updateAvailableCommandsAndNotifyIfChanged();
    } else if (pendingSeekCount == 0) {
        listeners.queueEvent(/* eventFlag= */
        C.INDEX_UNSET, Listener::onSeekProcessed);
    }
    listeners.flushEvents();
}
Also used : MediaItem(androidx.media3.common.MediaItem) MediaStatus(com.google.android.gms.cast.MediaStatus)

Example 3 with Listener

use of androidx.media3.common.Player.Listener in project media by androidx.

the class CastPlayerTest method updateTimeLine.

private void updateTimeLine(List<MediaItem> mediaItems, int[] mediaQueueItemIds, int currentItemId, int[] streamTypes, long[] durationsMs, long positionMs) {
    // Set up mocks to allow the player to update the timeline.
    List<MediaQueueItem> queueItems = new ArrayList<>();
    for (int i = 0; i < mediaQueueItemIds.length; i++) {
        MediaItem mediaItem = mediaItems.get(i);
        int mediaQueueItemId = mediaQueueItemIds[i];
        int streamType = streamTypes[i];
        long durationMs = durationsMs[i];
        MediaInfo.Builder mediaInfoBuilder = new MediaInfo.Builder(mediaItem.localConfiguration.uri.toString()).setStreamType(streamType).setContentType(mediaItem.localConfiguration.mimeType);
        if (durationMs != C.TIME_UNSET) {
            mediaInfoBuilder.setStreamDuration(durationMs);
        }
        MediaInfo mediaInfo = mediaInfoBuilder.build();
        MediaQueueItem mediaQueueItem = mock(MediaQueueItem.class);
        when(mediaQueueItem.getItemId()).thenReturn(mediaQueueItemId);
        when(mediaQueueItem.getMedia()).thenReturn(mediaInfo);
        queueItems.add(mediaQueueItem);
        if (mediaQueueItemId == currentItemId) {
            when(mockRemoteMediaClient.getCurrentItem()).thenReturn(mediaQueueItem);
            when(mockMediaStatus.getMediaInfo()).thenReturn(mediaInfo);
        }
    }
    if (positionMs != C.TIME_UNSET) {
        when(mockRemoteMediaClient.getApproximateStreamPosition()).thenReturn(positionMs);
    }
    when(mockMediaQueue.getItemIds()).thenReturn(mediaQueueItemIds);
    when(mockMediaStatus.getQueueItems()).thenReturn(queueItems);
    when(mockMediaStatus.getCurrentItemId()).thenReturn(currentItemId == C.INDEX_UNSET ? 0 : currentItemId);
    // Call listener to update the timeline of the player.
    remoteMediaClientCallback.onStatusUpdated();
}
Also used : MediaInfo(com.google.android.gms.cast.MediaInfo) MediaItem(androidx.media3.common.MediaItem) ArrayList(java.util.ArrayList) MediaQueueItem(com.google.android.gms.cast.MediaQueueItem)

Example 4 with Listener

use of androidx.media3.common.Player.Listener in project media by androidx.

the class DefaultRenderersFactory method buildAudioRenderers.

/**
 * Builds audio renderers for use by the player.
 *
 * @param context The {@link Context} associated with the player.
 * @param extensionRendererMode The extension renderer mode.
 * @param mediaCodecSelector A decoder selector.
 * @param enableDecoderFallback Whether to enable fallback to lower-priority decoders if decoder
 *     initialization fails. This may result in using a decoder that is slower/less efficient than
 *     the primary decoder.
 * @param audioSink A sink to which the renderers will output.
 * @param eventHandler A handler to use when invoking event listeners and outputs.
 * @param eventListener An event listener.
 * @param out An array to which the built renderers should be appended.
 */
protected void buildAudioRenderers(Context context, @ExtensionRendererMode int extensionRendererMode, MediaCodecSelector mediaCodecSelector, boolean enableDecoderFallback, AudioSink audioSink, Handler eventHandler, AudioRendererEventListener eventListener, ArrayList<Renderer> out) {
    MediaCodecAudioRenderer audioRenderer = new MediaCodecAudioRenderer(context, getCodecAdapterFactory(), mediaCodecSelector, enableDecoderFallback, eventHandler, eventListener, audioSink);
    out.add(audioRenderer);
    if (extensionRendererMode == EXTENSION_RENDERER_MODE_OFF) {
        return;
    }
    int extensionRendererIndex = out.size();
    if (extensionRendererMode == EXTENSION_RENDERER_MODE_PREFER) {
        extensionRendererIndex--;
    }
    try {
        // Full class names used for constructor args so the LINT rule triggers if any of them move.
        Class<?> clazz = Class.forName("androidx.media3.decoder.opus.LibopusAudioRenderer");
        Constructor<?> constructor = clazz.getConstructor(android.os.Handler.class, androidx.media3.exoplayer.audio.AudioRendererEventListener.class, androidx.media3.exoplayer.audio.AudioSink.class);
        Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, audioSink);
        out.add(extensionRendererIndex++, renderer);
        Log.i(TAG, "Loaded LibopusAudioRenderer.");
    } catch (ClassNotFoundException e) {
    // Expected if the app was built without the extension.
    } catch (Exception e) {
        // The extension is present, but instantiation failed.
        throw new RuntimeException("Error instantiating Opus extension", e);
    }
    try {
        // Full class names used for constructor args so the LINT rule triggers if any of them move.
        Class<?> clazz = Class.forName("androidx.media3.decoder.flac.LibflacAudioRenderer");
        Constructor<?> constructor = clazz.getConstructor(android.os.Handler.class, androidx.media3.exoplayer.audio.AudioRendererEventListener.class, androidx.media3.exoplayer.audio.AudioSink.class);
        Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, audioSink);
        out.add(extensionRendererIndex++, renderer);
        Log.i(TAG, "Loaded LibflacAudioRenderer.");
    } catch (ClassNotFoundException e) {
    // Expected if the app was built without the extension.
    } catch (Exception e) {
        // The extension is present, but instantiation failed.
        throw new RuntimeException("Error instantiating FLAC extension", e);
    }
    try {
        // Full class names used for constructor args so the LINT rule triggers if any of them move.
        Class<?> clazz = Class.forName("androidx.media3.decoder.ffmpeg.FfmpegAudioRenderer");
        Constructor<?> constructor = clazz.getConstructor(android.os.Handler.class, androidx.media3.exoplayer.audio.AudioRendererEventListener.class, androidx.media3.exoplayer.audio.AudioSink.class);
        Renderer renderer = (Renderer) constructor.newInstance(eventHandler, eventListener, audioSink);
        out.add(extensionRendererIndex++, renderer);
        Log.i(TAG, "Loaded FfmpegAudioRenderer.");
    } catch (ClassNotFoundException e) {
    // Expected if the app was built without the extension.
    } catch (Exception e) {
        // The extension is present, but instantiation failed.
        throw new RuntimeException("Error instantiating FFmpeg extension", e);
    }
}
Also used : MediaCodecAudioRenderer(androidx.media3.exoplayer.audio.MediaCodecAudioRenderer) MediaCodecAudioRenderer(androidx.media3.exoplayer.audio.MediaCodecAudioRenderer) TextRenderer(androidx.media3.exoplayer.text.TextRenderer) MediaCodecVideoRenderer(androidx.media3.exoplayer.video.MediaCodecVideoRenderer) CameraMotionRenderer(androidx.media3.exoplayer.video.spherical.CameraMotionRenderer) MetadataRenderer(androidx.media3.exoplayer.metadata.MetadataRenderer)

Example 5 with Listener

use of androidx.media3.common.Player.Listener in project media by androidx.

the class ExoPlayerImpl method updatePlaybackInfo.

// Calling deprecated listeners.
@SuppressWarnings("deprecation")
private void updatePlaybackInfo(PlaybackInfo playbackInfo, @TimelineChangeReason int timelineChangeReason, @PlayWhenReadyChangeReason int playWhenReadyChangeReason, boolean seekProcessed, boolean positionDiscontinuity, @DiscontinuityReason int positionDiscontinuityReason, long discontinuityWindowStartPositionUs, int oldMaskingMediaItemIndex) {
    // Assign playback info immediately such that all getters return the right values, but keep
    // snapshot of previous and new state so that listener invocations are triggered correctly.
    PlaybackInfo previousPlaybackInfo = this.playbackInfo;
    PlaybackInfo newPlaybackInfo = playbackInfo;
    this.playbackInfo = playbackInfo;
    Pair<Boolean, Integer> mediaItemTransitionInfo = evaluateMediaItemTransitionReason(newPlaybackInfo, previousPlaybackInfo, positionDiscontinuity, positionDiscontinuityReason, !previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline));
    boolean mediaItemTransitioned = mediaItemTransitionInfo.first;
    int mediaItemTransitionReason = mediaItemTransitionInfo.second;
    MediaMetadata newMediaMetadata = mediaMetadata;
    @Nullable MediaItem mediaItem = null;
    if (mediaItemTransitioned) {
        if (!newPlaybackInfo.timeline.isEmpty()) {
            int windowIndex = newPlaybackInfo.timeline.getPeriodByUid(newPlaybackInfo.periodId.periodUid, period).windowIndex;
            mediaItem = newPlaybackInfo.timeline.getWindow(windowIndex, window).mediaItem;
        }
        staticAndDynamicMediaMetadata = MediaMetadata.EMPTY;
    }
    if (mediaItemTransitioned || !previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) {
        staticAndDynamicMediaMetadata = staticAndDynamicMediaMetadata.buildUpon().populateFromMetadata(newPlaybackInfo.staticMetadata).build();
        newMediaMetadata = buildUpdatedMediaMetadata();
    }
    boolean metadataChanged = !newMediaMetadata.equals(mediaMetadata);
    mediaMetadata = newMediaMetadata;
    boolean playWhenReadyChanged = previousPlaybackInfo.playWhenReady != newPlaybackInfo.playWhenReady;
    boolean playbackStateChanged = previousPlaybackInfo.playbackState != newPlaybackInfo.playbackState;
    if (playbackStateChanged || playWhenReadyChanged) {
        updateWakeAndWifiLock();
    }
    boolean isLoadingChanged = previousPlaybackInfo.isLoading != newPlaybackInfo.isLoading;
    if (isLoadingChanged) {
        updatePriorityTaskManagerForIsLoadingChange(newPlaybackInfo.isLoading);
    }
    if (!previousPlaybackInfo.timeline.equals(newPlaybackInfo.timeline)) {
        listeners.queueEvent(Player.EVENT_TIMELINE_CHANGED, listener -> listener.onTimelineChanged(newPlaybackInfo.timeline, timelineChangeReason));
    }
    if (positionDiscontinuity) {
        PositionInfo previousPositionInfo = getPreviousPositionInfo(positionDiscontinuityReason, previousPlaybackInfo, oldMaskingMediaItemIndex);
        PositionInfo positionInfo = getPositionInfo(discontinuityWindowStartPositionUs);
        listeners.queueEvent(Player.EVENT_POSITION_DISCONTINUITY, listener -> {
            listener.onPositionDiscontinuity(positionDiscontinuityReason);
            listener.onPositionDiscontinuity(previousPositionInfo, positionInfo, positionDiscontinuityReason);
        });
    }
    if (mediaItemTransitioned) {
        @Nullable final MediaItem finalMediaItem = mediaItem;
        listeners.queueEvent(Player.EVENT_MEDIA_ITEM_TRANSITION, listener -> listener.onMediaItemTransition(finalMediaItem, mediaItemTransitionReason));
    }
    if (previousPlaybackInfo.playbackError != newPlaybackInfo.playbackError) {
        listeners.queueEvent(Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerErrorChanged(newPlaybackInfo.playbackError));
        if (newPlaybackInfo.playbackError != null) {
            listeners.queueEvent(Player.EVENT_PLAYER_ERROR, listener -> listener.onPlayerError(newPlaybackInfo.playbackError));
        }
    }
    if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
        trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
        TrackSelectionArray newSelection = new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
        listeners.queueEvent(Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
        listeners.queueEvent(Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(newPlaybackInfo.trackSelectorResult.tracksInfo));
    }
    if (metadataChanged) {
        final MediaMetadata finalMediaMetadata = mediaMetadata;
        listeners.queueEvent(EVENT_MEDIA_METADATA_CHANGED, listener -> listener.onMediaMetadataChanged(finalMediaMetadata));
    }
    if (isLoadingChanged) {
        listeners.queueEvent(Player.EVENT_IS_LOADING_CHANGED, listener -> {
            listener.onLoadingChanged(newPlaybackInfo.isLoading);
            listener.onIsLoadingChanged(newPlaybackInfo.isLoading);
        });
    }
    if (playbackStateChanged || playWhenReadyChanged) {
        listeners.queueEvent(/* eventFlag= */
        C.INDEX_UNSET, listener -> listener.onPlayerStateChanged(newPlaybackInfo.playWhenReady, newPlaybackInfo.playbackState));
    }
    if (playbackStateChanged) {
        listeners.queueEvent(Player.EVENT_PLAYBACK_STATE_CHANGED, listener -> listener.onPlaybackStateChanged(newPlaybackInfo.playbackState));
    }
    if (playWhenReadyChanged) {
        listeners.queueEvent(Player.EVENT_PLAY_WHEN_READY_CHANGED, listener -> listener.onPlayWhenReadyChanged(newPlaybackInfo.playWhenReady, playWhenReadyChangeReason));
    }
    if (previousPlaybackInfo.playbackSuppressionReason != newPlaybackInfo.playbackSuppressionReason) {
        listeners.queueEvent(Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, listener -> listener.onPlaybackSuppressionReasonChanged(newPlaybackInfo.playbackSuppressionReason));
    }
    if (isPlaying(previousPlaybackInfo) != isPlaying(newPlaybackInfo)) {
        listeners.queueEvent(Player.EVENT_IS_PLAYING_CHANGED, listener -> listener.onIsPlayingChanged(isPlaying(newPlaybackInfo)));
    }
    if (!previousPlaybackInfo.playbackParameters.equals(newPlaybackInfo.playbackParameters)) {
        listeners.queueEvent(Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, listener -> listener.onPlaybackParametersChanged(newPlaybackInfo.playbackParameters));
    }
    if (seekProcessed) {
        listeners.queueEvent(/* eventFlag= */
        C.INDEX_UNSET, Listener::onSeekProcessed);
    }
    updateAvailableCommands();
    listeners.flushEvents();
    if (previousPlaybackInfo.offloadSchedulingEnabled != newPlaybackInfo.offloadSchedulingEnabled) {
        for (AudioOffloadListener listener : audioOffloadListeners) {
            listener.onExperimentalOffloadSchedulingEnabledChanged(newPlaybackInfo.offloadSchedulingEnabled);
        }
    }
    if (previousPlaybackInfo.sleepingForOffload != newPlaybackInfo.sleepingForOffload) {
        for (AudioOffloadListener listener : audioOffloadListeners) {
            listener.onExperimentalSleepingForOffloadChanged(newPlaybackInfo.sleepingForOffload);
        }
    }
}
Also used : AudioRendererEventListener(androidx.media3.exoplayer.audio.AudioRendererEventListener) VideoRendererEventListener(androidx.media3.exoplayer.video.VideoRendererEventListener) AnalyticsListener(androidx.media3.exoplayer.analytics.AnalyticsListener) VideoFrameMetadataListener(androidx.media3.exoplayer.video.VideoFrameMetadataListener) CameraMotionListener(androidx.media3.exoplayer.video.spherical.CameraMotionListener) SuppressLint(android.annotation.SuppressLint) TrackSelectionArray(androidx.media3.common.TrackSelectionArray) MediaItem(androidx.media3.common.MediaItem) MediaMetadata(androidx.media3.common.MediaMetadata) Nullable(androidx.annotation.Nullable)

Aggregations

Test (org.junit.Test)216 Player (androidx.media3.common.Player)173 CountDownLatch (java.util.concurrent.CountDownLatch)143 AtomicReference (java.util.concurrent.atomic.AtomicReference)108 LargeTest (androidx.test.filters.LargeTest)102 AtomicInteger (java.util.concurrent.atomic.AtomicInteger)102 MediaItem (androidx.media3.common.MediaItem)70 Timeline (androidx.media3.common.Timeline)70 EventTime (androidx.media3.exoplayer.analytics.AnalyticsListener.EventTime)68 FakeMediaSource (androidx.media3.test.utils.FakeMediaSource)58 PositionInfo (androidx.media3.common.Player.PositionInfo)56 TestExoPlayerBuilder (androidx.media3.test.utils.TestExoPlayerBuilder)54 Bundle (android.os.Bundle)50 Listener (androidx.media3.common.Player.Listener)45 ArrayList (java.util.ArrayList)45 Nullable (androidx.annotation.Nullable)42 RemoteMockPlayer (androidx.media3.session.RemoteMediaSession.RemoteMockPlayer)40 ConcatenatingMediaSource (androidx.media3.exoplayer.source.ConcatenatingMediaSource)36 AtomicLong (java.util.concurrent.atomic.AtomicLong)36 MediaSource (androidx.media3.exoplayer.source.MediaSource)35