mirror of
https://github.com/androidx/media.git
synced 2025-05-08 16:10:38 +08:00
Move RtspClient creation into RtspPeriod.
RtspMediaSource uses the timeline update paradigm from ProgressiveMediaPeriod. #minor-release PiperOrigin-RevId: 378150758
This commit is contained in:
parent
22b126cac3
commit
1ca0efdd9b
@ -96,16 +96,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
private final SessionInfoListener sessionInfoListener;
|
||||
private final PlaybackEventListener playbackEventListener;
|
||||
private final Uri uri;
|
||||
@Nullable private final RtspAuthUserInfo rtspAuthUserInfo;
|
||||
@Nullable private final String userAgent;
|
||||
private final String userAgent;
|
||||
private final ArrayDeque<RtpLoadInfo> pendingSetupRtpLoadInfos;
|
||||
// TODO(b/172331505) Add a timeout monitor for pending requests.
|
||||
private final SparseArray<RtspRequest> pendingRequests;
|
||||
private final MessageSender messageSender;
|
||||
|
||||
private RtspMessageChannel messageChannel;
|
||||
private @MonotonicNonNull PlaybackEventListener playbackEventListener;
|
||||
@Nullable private String sessionId;
|
||||
@Nullable private KeepAliveMonitor keepAliveMonitor;
|
||||
@Nullable private RtspAuthenticationInfo rtspAuthenticationInfo;
|
||||
@ -123,12 +123,17 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
* <p>Note: all method invocations must be made from the playback thread.
|
||||
*
|
||||
* @param sessionInfoListener The {@link SessionInfoListener}.
|
||||
* @param userAgent The user agent that will be used if needed, or {@code null} for the fallback
|
||||
* to use the default user agent of the underlying platform.
|
||||
* @param playbackEventListener The {@link PlaybackEventListener}.
|
||||
* @param userAgent The user agent.
|
||||
* @param uri The RTSP playback URI.
|
||||
*/
|
||||
public RtspClient(SessionInfoListener sessionInfoListener, @Nullable String userAgent, Uri uri) {
|
||||
public RtspClient(
|
||||
SessionInfoListener sessionInfoListener,
|
||||
PlaybackEventListener playbackEventListener,
|
||||
String userAgent,
|
||||
Uri uri) {
|
||||
this.sessionInfoListener = sessionInfoListener;
|
||||
this.playbackEventListener = playbackEventListener;
|
||||
this.uri = RtspMessageUtil.removeUserInfo(uri);
|
||||
this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(uri);
|
||||
this.userAgent = userAgent;
|
||||
@ -157,17 +162,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
messageSender.sendOptionsRequest(uri, sessionId);
|
||||
}
|
||||
|
||||
/** Sets the {@link PlaybackEventListener} to receive playback events. */
|
||||
public void setPlaybackEventListener(PlaybackEventListener playbackEventListener) {
|
||||
this.playbackEventListener = playbackEventListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers RTSP SETUP requests after track selection.
|
||||
*
|
||||
* <p>A {@link PlaybackEventListener} must be set via {@link #setPlaybackEventListener} before
|
||||
* calling this method. All selected tracks (represented by {@link RtpLoadInfo}) must have valid
|
||||
* transport.
|
||||
* <p>All selected tracks (represented by {@link RtpLoadInfo}) must have valid transport.
|
||||
*
|
||||
* @param loadInfos A list of selected tracks represented by {@link RtpLoadInfo}.
|
||||
*/
|
||||
@ -224,7 +222,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
receivedAuthorizationRequest = false;
|
||||
rtspAuthenticationInfo = null;
|
||||
} catch (IOException e) {
|
||||
checkNotNull(playbackEventListener).onPlaybackError(new RtspPlaybackException(e));
|
||||
playbackEventListener.onPlaybackError(new RtspPlaybackException(e));
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +235,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private void continueSetupRtspTrack() {
|
||||
@Nullable RtpLoadInfo loadInfo = pendingSetupRtpLoadInfos.pollFirst();
|
||||
if (loadInfo == null) {
|
||||
checkNotNull(playbackEventListener).onRtspSetupCompleted();
|
||||
playbackEventListener.onRtspSetupCompleted();
|
||||
return;
|
||||
}
|
||||
messageSender.sendSetupRequest(loadInfo.getTrackUri(), loadInfo.getTransport(), sessionId);
|
||||
@ -258,7 +256,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
if (hasUpdatedTimelineAndTracks) {
|
||||
// Playback event listener must be non-null after timeline has been updated.
|
||||
checkNotNull(playbackEventListener).onPlaybackError(playbackException);
|
||||
playbackEventListener.onPlaybackError(playbackException);
|
||||
} else {
|
||||
sessionInfoListener.onSessionTimelineRequestFailed(nullToEmpty(error.getMessage()), error);
|
||||
}
|
||||
@ -373,9 +371,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
Uri uri) {
|
||||
RtspHeaders.Builder headersBuilder = new RtspHeaders.Builder();
|
||||
headersBuilder.add(RtspHeaders.CSEQ, String.valueOf(cSeq++));
|
||||
if (userAgent != null) {
|
||||
headersBuilder.add(RtspHeaders.USER_AGENT, userAgent);
|
||||
}
|
||||
|
||||
if (sessionId != null) {
|
||||
headersBuilder.add(RtspHeaders.SESSION, sessionId);
|
||||
@ -574,8 +570,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
keepAliveMonitor.start();
|
||||
}
|
||||
|
||||
checkNotNull(playbackEventListener)
|
||||
.onPlaybackStarted(
|
||||
playbackEventListener.onPlaybackStarted(
|
||||
C.msToUs(response.sessionTiming.startTimeMs), response.trackTimingList);
|
||||
pendingSeekPositionUs = C.TIME_UNSET;
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import com.google.android.exoplayer2.source.SampleStream.ReadFlags;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener;
|
||||
import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener;
|
||||
import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
@ -62,22 +63,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
/** A {@link MediaPeriod} that loads an RTSP stream. */
|
||||
/* package */ final class RtspMediaPeriod implements MediaPeriod {
|
||||
|
||||
/** Listener for information about the period. */
|
||||
interface Listener {
|
||||
|
||||
/** Called when the {@link RtspSessionTiming} is available. */
|
||||
void onSourceInfoRefreshed(RtspSessionTiming timing);
|
||||
}
|
||||
|
||||
/** The maximum times to retry if the underlying data channel failed to bind. */
|
||||
private static final int PORT_BINDING_MAX_RETRY_COUNT = 3;
|
||||
|
||||
private final Allocator allocator;
|
||||
private final Handler handler;
|
||||
|
||||
private final InternalListener internalListener;
|
||||
private final RtspClient rtspClient;
|
||||
private final List<RtspLoaderWrapper> rtspLoaderWrappers;
|
||||
private final List<RtpLoadInfo> selectedLoadInfos;
|
||||
private final Listener listener;
|
||||
private final RtpDataChannel.Factory rtpDataChannelFactory;
|
||||
|
||||
private @MonotonicNonNull Callback callback;
|
||||
private @MonotonicNonNull ImmutableList<TrackGroup> trackGroups;
|
||||
@Nullable private IOException preparationError;
|
||||
@Nullable private RtspPlaybackException playbackException;
|
||||
|
||||
private long lastSeekPositionUs;
|
||||
private long pendingSeekPositionUs;
|
||||
private boolean loadingFinished;
|
||||
private boolean released;
|
||||
@ -90,29 +100,31 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
* Creates an RTSP media period.
|
||||
*
|
||||
* @param allocator An {@link Allocator} from which to obtain media buffer allocations.
|
||||
* @param rtspTracks A list of tracks in an RTSP playback session.
|
||||
* @param rtspClient The {@link RtspClient} for the current RTSP playback.
|
||||
* @param rtpDataChannelFactory A {@link RtpDataChannel.Factory} for {@link RtpDataChannel}.
|
||||
* @param uri The RTSP playback {@link Uri}.
|
||||
* @param listener A {@link Listener} to receive session information updates.
|
||||
*/
|
||||
public RtspMediaPeriod(
|
||||
Allocator allocator,
|
||||
List<RtspMediaTrack> rtspTracks,
|
||||
RtspClient rtspClient,
|
||||
RtpDataChannel.Factory rtpDataChannelFactory) {
|
||||
RtpDataChannel.Factory rtpDataChannelFactory,
|
||||
Uri uri,
|
||||
Listener listener,
|
||||
String userAgent) {
|
||||
this.allocator = allocator;
|
||||
this.rtpDataChannelFactory = rtpDataChannelFactory;
|
||||
this.listener = listener;
|
||||
|
||||
handler = Util.createHandlerForCurrentLooper();
|
||||
|
||||
internalListener = new InternalListener();
|
||||
rtspLoaderWrappers = new ArrayList<>(rtspTracks.size());
|
||||
this.rtspClient = rtspClient;
|
||||
this.rtspClient.setPlaybackEventListener(internalListener);
|
||||
rtspClient =
|
||||
new RtspClient(
|
||||
/* sessionInfoListener= */ internalListener,
|
||||
/* playbackEventListener= */ internalListener,
|
||||
/* userAgent= */ userAgent,
|
||||
/* uri= */ uri);
|
||||
rtspLoaderWrappers = new ArrayList<>();
|
||||
selectedLoadInfos = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < rtspTracks.size(); i++) {
|
||||
RtspMediaTrack rtspMediaTrack = rtspTracks.get(i);
|
||||
rtspLoaderWrappers.add(
|
||||
new RtspLoaderWrapper(rtspMediaTrack, /* trackId= */ i, rtpDataChannelFactory));
|
||||
}
|
||||
selectedLoadInfos = new ArrayList<>(rtspTracks.size());
|
||||
pendingSeekPositionUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@ -121,6 +133,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
for (int i = 0; i < rtspLoaderWrappers.size(); i++) {
|
||||
rtspLoaderWrappers.get(i).release();
|
||||
}
|
||||
Util.closeQuietly(rtspClient);
|
||||
released = true;
|
||||
}
|
||||
|
||||
@ -128,8 +141,11 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
public void prepare(Callback callback, long positionUs) {
|
||||
this.callback = callback;
|
||||
|
||||
for (int i = 0; i < rtspLoaderWrappers.size(); i++) {
|
||||
rtspLoaderWrappers.get(i).startLoading();
|
||||
try {
|
||||
rtspClient.start();
|
||||
} catch (IOException e) {
|
||||
preparationError = e;
|
||||
Util.closeQuietly(rtspClient);
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,6 +249,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
return positionUs;
|
||||
}
|
||||
|
||||
lastSeekPositionUs = positionUs;
|
||||
pendingSeekPositionUs = positionUs;
|
||||
rtspClient.seekToUs(positionUs);
|
||||
for (int i = 0; i < rtspLoaderWrappers.size(); i++) {
|
||||
@ -256,14 +273,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
return pendingSeekPositionUs;
|
||||
}
|
||||
|
||||
long bufferedPositionUs = rtspLoaderWrappers.get(0).sampleQueue.getLargestQueuedTimestampUs();
|
||||
for (int i = 1; i < rtspLoaderWrappers.size(); i++) {
|
||||
bufferedPositionUs =
|
||||
min(
|
||||
bufferedPositionUs,
|
||||
checkNotNull(rtspLoaderWrappers.get(i)).sampleQueue.getLargestQueuedTimestampUs());
|
||||
boolean allLoaderWrappersAreCanceled = true;
|
||||
long bufferedPositionUs = Long.MAX_VALUE;
|
||||
for (int i = 0; i < rtspLoaderWrappers.size(); i++) {
|
||||
RtspLoaderWrapper loaderWrapper = rtspLoaderWrappers.get(i);
|
||||
if (!loaderWrapper.canceled) {
|
||||
bufferedPositionUs = min(bufferedPositionUs, loaderWrapper.getBufferedPositionUs());
|
||||
allLoaderWrappersAreCanceled = false;
|
||||
}
|
||||
return bufferedPositionUs;
|
||||
}
|
||||
|
||||
return allLoaderWrappersAreCanceled || bufferedPositionUs == Long.MIN_VALUE
|
||||
? lastSeekPositionUs
|
||||
: bufferedPositionUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -386,6 +408,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
implements ExtractorOutput,
|
||||
Loader.Callback<RtpDataLoadable>,
|
||||
UpstreamFormatChangedListener,
|
||||
SessionInfoListener,
|
||||
PlaybackEventListener {
|
||||
|
||||
// ExtractorOutput implementation.
|
||||
@ -515,7 +538,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
/** Handles the {@link Loadable} whose {@link RtpDataChannel} timed out. */
|
||||
private LoadErrorAction handleSocketTimeout(RtpDataLoadable loadable) {
|
||||
// TODO(b/172331505) Allow for retry when loading is not ending.
|
||||
if (getBufferedPositionUs() == Long.MIN_VALUE) {
|
||||
if (getBufferedPositionUs() == 0) {
|
||||
if (!isUsingRtpTcp) {
|
||||
// Retry playback with TCP if no sample has been received so far, and we are not already
|
||||
// using TCP. Retrying will setup new loadables, so will not retry with the current
|
||||
@ -533,9 +556,27 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
break;
|
||||
}
|
||||
}
|
||||
playbackException = new RtspPlaybackException("Unknown loadable timed out.");
|
||||
return Loader.DONT_RETRY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionTimelineUpdated(
|
||||
RtspSessionTiming timing, ImmutableList<RtspMediaTrack> tracks) {
|
||||
for (int i = 0; i < tracks.size(); i++) {
|
||||
RtspMediaTrack rtspMediaTrack = tracks.get(i);
|
||||
RtspLoaderWrapper loaderWrapper =
|
||||
new RtspLoaderWrapper(rtspMediaTrack, /* trackId= */ i, rtpDataChannelFactory);
|
||||
loaderWrapper.startLoading();
|
||||
rtspLoaderWrappers.add(loaderWrapper);
|
||||
}
|
||||
|
||||
listener.onSourceInfoRefreshed(timing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionTimelineRequestFailed(String message, @Nullable Throwable cause) {
|
||||
preparationError = cause == null ? new IOException(message) : new IOException(message, cause);
|
||||
}
|
||||
}
|
||||
|
||||
private void retryWithRtpTcp() {
|
||||
@ -632,6 +673,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
sampleQueue.setUpstreamFormatChangeListener(internalListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the largest buffered position in microseconds; or {@link Long#MIN_VALUE} if no sample
|
||||
* has been queued.
|
||||
*/
|
||||
public long getBufferedPositionUs() {
|
||||
return sampleQueue.getLargestQueuedTimestampUs();
|
||||
}
|
||||
|
||||
/** Starts loading. */
|
||||
public void startLoading() {
|
||||
loader.startLoading(
|
||||
|
@ -16,30 +16,27 @@
|
||||
|
||||
package com.google.android.exoplayer2.source.rtsp;
|
||||
|
||||
import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.VERSION_SLASHY;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.source.BaseMediaSource;
|
||||
import com.google.android.exoplayer2.source.ForwardingTimeline;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
||||
import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
||||
import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.io.IOException;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/** An Rtsp {@link MediaSource} */
|
||||
public final class RtspMediaSource extends BaseMediaSource {
|
||||
@ -63,8 +60,13 @@ public final class RtspMediaSource extends BaseMediaSource {
|
||||
*/
|
||||
public static final class Factory implements MediaSourceFactory {
|
||||
|
||||
private String userAgent;
|
||||
private boolean forceUseRtpTcp;
|
||||
|
||||
public Factory() {
|
||||
userAgent = ExoPlayerLibraryInfo.VERSION_SLASHY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to force using TCP as the default RTP transport.
|
||||
*
|
||||
@ -81,6 +83,17 @@ public final class RtspMediaSource extends BaseMediaSource {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the user agent, the default value is {@link ExoPlayerLibraryInfo#VERSION_SLASHY}.
|
||||
*
|
||||
* @param userAgent The user agent.
|
||||
* @return This Factory, for convenience.
|
||||
*/
|
||||
public Factory setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Does nothing. {@link RtspMediaSource} does not support DRM. */
|
||||
@Override
|
||||
public Factory setDrmSessionManagerProvider(
|
||||
@ -149,7 +162,8 @@ public final class RtspMediaSource extends BaseMediaSource {
|
||||
mediaItem,
|
||||
forceUseRtpTcp
|
||||
? new TransferRtpDataChannelFactory()
|
||||
: new UdpDataSourceRtpDataChannelFactory());
|
||||
: new UdpDataSourceRtpDataChannelFactory(),
|
||||
userAgent);
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,34 +184,32 @@ public final class RtspMediaSource extends BaseMediaSource {
|
||||
|
||||
private final MediaItem mediaItem;
|
||||
private final RtpDataChannel.Factory rtpDataChannelFactory;
|
||||
private @MonotonicNonNull RtspClient rtspClient;
|
||||
private final String userAgent;
|
||||
private final Uri uri;
|
||||
|
||||
@Nullable private ImmutableList<RtspMediaTrack> rtspMediaTracks;
|
||||
@Nullable private IOException sourcePrepareException;
|
||||
private long timelineDurationUs;
|
||||
private boolean timelineIsSeekable;
|
||||
private boolean timelineIsLive;
|
||||
private boolean timelineIsPlaceholder;
|
||||
|
||||
private RtspMediaSource(MediaItem mediaItem, RtpDataChannel.Factory rtpDataChannelFactory) {
|
||||
private RtspMediaSource(
|
||||
MediaItem mediaItem, RtpDataChannel.Factory rtpDataChannelFactory, String userAgent) {
|
||||
this.mediaItem = mediaItem;
|
||||
this.rtpDataChannelFactory = rtpDataChannelFactory;
|
||||
this.userAgent = userAgent;
|
||||
this.uri = checkNotNull(this.mediaItem.playbackProperties).uri;
|
||||
this.timelineDurationUs = C.TIME_UNSET;
|
||||
this.timelineIsPlaceholder = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||
checkNotNull(mediaItem.playbackProperties);
|
||||
try {
|
||||
rtspClient =
|
||||
new RtspClient(
|
||||
new SessionInfoListenerImpl(),
|
||||
/* userAgent= */ VERSION_SLASHY,
|
||||
mediaItem.playbackProperties.uri);
|
||||
rtspClient.start();
|
||||
} catch (IOException e) {
|
||||
sourcePrepareException = new RtspPlaybackException("RtspClient not opened.", e);
|
||||
}
|
||||
notifySourceInfoRefreshed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void releaseSourceInternal() {
|
||||
Util.closeQuietly(rtspClient);
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -206,16 +218,24 @@ public final class RtspMediaSource extends BaseMediaSource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||
if (sourcePrepareException != null) {
|
||||
throw sourcePrepareException;
|
||||
}
|
||||
public void maybeThrowSourceInfoRefreshError() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||
return new RtspMediaPeriod(
|
||||
allocator, checkNotNull(rtspMediaTracks), checkNotNull(rtspClient), rtpDataChannelFactory);
|
||||
allocator,
|
||||
rtpDataChannelFactory,
|
||||
uri,
|
||||
(timing) -> {
|
||||
timelineDurationUs = C.msToUs(timing.getDurationMs());
|
||||
timelineIsSeekable = !timing.isLive();
|
||||
timelineIsLive = timing.isLive();
|
||||
timelineIsPlaceholder = false;
|
||||
notifySourceInfoRefreshed();
|
||||
},
|
||||
userAgent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -223,28 +243,36 @@ public final class RtspMediaSource extends BaseMediaSource {
|
||||
((RtspMediaPeriod) mediaPeriod).release();
|
||||
}
|
||||
|
||||
private final class SessionInfoListenerImpl implements SessionInfoListener {
|
||||
@Override
|
||||
public void onSessionTimelineUpdated(
|
||||
RtspSessionTiming timing, ImmutableList<RtspMediaTrack> tracks) {
|
||||
rtspMediaTracks = tracks;
|
||||
refreshSourceInfo(
|
||||
// Internal methods.
|
||||
|
||||
private void notifySourceInfoRefreshed() {
|
||||
Timeline timeline =
|
||||
new SinglePeriodTimeline(
|
||||
/* durationUs= */ C.msToUs(timing.getDurationMs()),
|
||||
/* isSeekable= */ !timing.isLive(),
|
||||
timelineDurationUs,
|
||||
timelineIsSeekable,
|
||||
/* isDynamic= */ false,
|
||||
/* useLiveConfiguration= */ timing.isLive(),
|
||||
/* useLiveConfiguration= */ timelineIsLive,
|
||||
/* manifest= */ null,
|
||||
mediaItem));
|
||||
mediaItem);
|
||||
if (timelineIsPlaceholder) {
|
||||
timeline =
|
||||
new ForwardingTimeline(timeline) {
|
||||
@Override
|
||||
public Window getWindow(
|
||||
int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||
super.getWindow(windowIndex, window, defaultPositionProjectionUs);
|
||||
window.isPlaceholder = true;
|
||||
return window;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSessionTimelineRequestFailed(String message, @Nullable Throwable cause) {
|
||||
if (cause == null) {
|
||||
sourcePrepareException = new RtspPlaybackException(message);
|
||||
} else {
|
||||
sourcePrepareException = new RtspPlaybackException(message, castNonNull(cause));
|
||||
}
|
||||
}
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
super.getPeriod(periodIndex, period, setIds);
|
||||
period.isPlaceholder = true;
|
||||
return period;
|
||||
}
|
||||
};
|
||||
}
|
||||
refreshSourceInfo(timeline);
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ import androidx.annotation.Nullable;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.robolectric.RobolectricUtil;
|
||||
import com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener;
|
||||
import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener;
|
||||
import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@ -76,6 +78,17 @@ public final class RtspClientTest {
|
||||
public void onSessionTimelineRequestFailed(
|
||||
String message, @Nullable Throwable cause) {}
|
||||
},
|
||||
new PlaybackEventListener() {
|
||||
@Override
|
||||
public void onRtspSetupCompleted() {}
|
||||
|
||||
@Override
|
||||
public void onPlaybackStarted(
|
||||
long startPositionUs, ImmutableList<RtspTrackTiming> trackTimingList) {}
|
||||
|
||||
@Override
|
||||
public void onPlaybackError(RtspPlaybackException error) {}
|
||||
},
|
||||
/* userAgent= */ "ExoPlayer:RtspClientTest",
|
||||
/* uri= */ Uri.parse(
|
||||
Util.formatInvariant("rtsp://localhost:%d/test", serverRtspPortNumber)));
|
||||
|
@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 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.exoplayer2.source.rtsp;
|
||||
|
||||
import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.annotation.internal.DoNotInstrument;
|
||||
|
||||
/** Unit test for {@link RtspMediaPeriod}. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@DoNotInstrument
|
||||
public class RtspMediaPeriodTest {
|
||||
|
||||
private static final RtspClient PLACEHOLDER_RTSP_CLIENT =
|
||||
new RtspClient(
|
||||
new RtspClient.SessionInfoListener() {
|
||||
@Override
|
||||
public void onSessionTimelineUpdated(
|
||||
RtspSessionTiming timing, ImmutableList<RtspMediaTrack> tracks) {}
|
||||
|
||||
@Override
|
||||
public void onSessionTimelineRequestFailed(String message, @Nullable Throwable cause) {}
|
||||
},
|
||||
/* userAgent= */ null,
|
||||
Uri.EMPTY);
|
||||
|
||||
@Test
|
||||
public void prepare_startsLoading() throws Exception {
|
||||
RtspMediaPeriod rtspMediaPeriod =
|
||||
new RtspMediaPeriod(
|
||||
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||
ImmutableList.of(
|
||||
new RtspMediaTrack(
|
||||
new MediaDescription.Builder(
|
||||
/* mediaType= */ MediaDescription.MEDIA_TYPE_VIDEO,
|
||||
/* port= */ 0,
|
||||
/* transportProtocol= */ MediaDescription.RTP_AVP_PROFILE,
|
||||
/* payloadType= */ 96)
|
||||
.setConnection("IN IP4 0.0.0.0")
|
||||
.setBitrate(500_000)
|
||||
.addAttribute(SessionDescription.ATTR_RTPMAP, "96 H264/90000")
|
||||
.addAttribute(
|
||||
SessionDescription.ATTR_FMTP,
|
||||
"96 packetization-mode=1;profile-level-id=64001F;sprop-parameter-sets=Z2QAH6zZQPARabIAAAMACAAAAwGcHjBjLA==,aOvjyyLA")
|
||||
.addAttribute(SessionDescription.ATTR_CONTROL, "track1")
|
||||
.build(),
|
||||
Uri.parse("rtsp://localhost/test"))),
|
||||
PLACEHOLDER_RTSP_CLIENT,
|
||||
new UdpDataSourceRtpDataChannelFactory());
|
||||
|
||||
AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false);
|
||||
rtspMediaPeriod.prepare(
|
||||
new MediaPeriod.Callback() {
|
||||
@Override
|
||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||
prepareCallbackCalled.set(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContinueLoadingRequested(MediaPeriod source) {
|
||||
source.continueLoading(/* positionUs= */ 0);
|
||||
}
|
||||
},
|
||||
/* positionUs= */ 0);
|
||||
|
||||
runMainLooperUntil(prepareCallbackCalled::get);
|
||||
rtspMediaPeriod.release();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBufferedPositionUs_withNoRtspMediaTracks_returnsEndOfSource() {
|
||||
RtspMediaPeriod rtspMediaPeriod =
|
||||
new RtspMediaPeriod(
|
||||
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||
ImmutableList.of(),
|
||||
PLACEHOLDER_RTSP_CLIENT,
|
||||
new UdpDataSourceRtpDataChannelFactory());
|
||||
|
||||
assertThat(rtspMediaPeriod.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user