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

Allows exact live sync for HLS #1752

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions demos/main/src/main/java/androidx/media3/demo/main/IntentUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration;
import androidx.media3.common.MediaItem.LiveConfiguration;
import androidx.media3.common.MediaItem.SubtitleConfiguration;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
Expand Down Expand Up @@ -86,6 +87,9 @@ public class IntentUtil {
}
}

public static final String LIVE_OFFSET_TARGET_VALUE = "live_offset_target";
public static final String LIVE_OFFSET_ADJUSTMENT_SPEED = "live_offset_adjust_speed";

/** Creates a list of {@link MediaItem media items} from an {@link Intent}. */
public static List<MediaItem> createMediaItemsFromIntent(Intent intent) {
List<MediaItem> mediaItems = new ArrayList<>();
Expand Down Expand Up @@ -143,6 +147,9 @@ private static MediaItem createMediaItemFromIntent(
SubtitleConfiguration subtitleConfiguration =
createSubtitleConfiguration(intent, extrasKeySuffix);
long imageDurationMs = intent.getLongExtra(IMAGE_DURATION_MS + extrasKeySuffix, C.TIME_UNSET);
@Nullable
LiveConfiguration liveConfiguration = createLiveConfiguration(intent, extrasKeySuffix);

MediaItem.Builder builder =
new MediaItem.Builder()
.setUri(uri)
Expand All @@ -165,9 +172,37 @@ private static MediaItem createMediaItemFromIntent(
builder.setSubtitleConfigurations(ImmutableList.of(subtitleConfiguration));
}

if (liveConfiguration != null) {
builder.setLiveConfiguration(liveConfiguration);
}

return populateDrmPropertiesFromIntent(builder, intent, extrasKeySuffix).build();
}

@Nullable
private static LiveConfiguration createLiveConfiguration(Intent intent, String extrasKeySuffix) {
LiveConfiguration.Builder builder = null;
boolean fast_resync = intent.hasExtra(LIVE_OFFSET_ADJUSTMENT_SPEED + extrasKeySuffix);
if (fast_resync) {
float resyncPercentChange = intent.getFloatExtra(LIVE_OFFSET_ADJUSTMENT_SPEED, 0.0f) / 100.0f;
builder = new LiveConfiguration.Builder();
builder
.setMaxPlaybackSpeed(1.0f + resyncPercentChange)
.setMinPlaybackSpeed(1.0f - resyncPercentChange);
}

if (intent.hasExtra(LIVE_OFFSET_TARGET_VALUE)) {
int liveTargetOffsetMs = intent.getIntExtra(LIVE_OFFSET_TARGET_VALUE, 0) * 1000;
builder = builder == null ? new LiveConfiguration.Builder() : builder;

builder.setTargetOffsetMs(liveTargetOffsetMs);
if (fast_resync) {
builder.setMinOffsetMs(liveTargetOffsetMs).setMinOffsetMs(liveTargetOffsetMs);
}
}
return builder == null ? null : builder.build();
}

@Nullable
private static MediaItem.SubtitleConfiguration createSubtitleConfiguration(
Intent intent, String extrasKeySuffix) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import androidx.media3.exoplayer.upstream.CmcdConfiguration;
import androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import androidx.media3.exoplayer.util.SntpClient;
import androidx.media3.extractor.Extractor;
import androidx.media3.extractor.text.SubtitleParser;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
Expand Down Expand Up @@ -598,7 +599,7 @@ private SinglePeriodTimeline createTimelineForLive(
return new SinglePeriodTimeline(
presentationStartTimeMs,
windowStartTimeMs,
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
SntpClient.getElapsedRealtimeOffsetMs(),
periodDurationUs,
/* windowDurationUs= */ playlist.durationUs,
/* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
import static androidx.media3.common.util.Util.castNonNull;
import static java.lang.Math.max;

import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.Handler;
import android.os.SystemClock;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
Expand All @@ -43,6 +45,7 @@
import androidx.media3.exoplayer.upstream.Loader;
import androidx.media3.exoplayer.upstream.Loader.LoadErrorAction;
import androidx.media3.exoplayer.upstream.ParsingLoadable;
import androidx.media3.exoplayer.util.SntpClient;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.util.HashMap;
Expand All @@ -54,6 +57,8 @@
public final class DefaultHlsPlaylistTracker
implements HlsPlaylistTracker, Loader.Callback<ParsingLoadable<HlsPlaylist>> {

public static final String TAG = "DefaultHlsPlaylistTracker";

/** Factory for {@link DefaultHlsPlaylistTracker} instances. */
public static final Factory FACTORY = DefaultHlsPlaylistTracker::new;

Expand Down Expand Up @@ -141,6 +146,25 @@ public void start(
playlistParserFactory.createPlaylistParser());
Assertions.checkState(initialPlaylistLoader == null);
initialPlaylistLoader = new Loader("DefaultHlsPlaylistTracker:MultivariantPlaylist");
SntpClient.InitializationCallback callback =
new SntpClient.InitializationCallback() {
@Override
public void onInitialized() {
requestMasterPlaylist(eventDispatcher, multivariantPlaylistLoadable);
}

@SuppressLint("Range")
@Override
public void onInitializationFailed(IOException error) {
Log.w(TAG, "NTP time init failed, use default system time", error);
requestMasterPlaylist(eventDispatcher, multivariantPlaylistLoadable);
}
};
SntpClient.initialize(initialPlaylistLoader, callback);
}

private void requestMasterPlaylist(
EventDispatcher eventDispatcher, ParsingLoadable<HlsPlaylist> multivariantPlaylistLoadable) {
long elapsedRealtime =
initialPlaylistLoader.startLoading(
multivariantPlaylistLoadable,
Expand Down Expand Up @@ -728,6 +752,27 @@ private void loadPlaylistInternal(Uri playlistRequestUri) {
}

private void loadPlaylistImmediately(Uri playlistRequestUri) {
if (primaryMediaPlaylistUrl == null || playlistRequestUri.equals(primaryMediaPlaylistUrl)) {
SntpClient.InitializationCallback callback =
new SntpClient.InitializationCallback() {
@Override
public void onInitialized() {
loadAfterTimeSync(playlistRequestUri);
}

@Override
public void onInitializationFailed(IOException error) {
Log.w(TAG, "NTP time init failed, use default system time", error);
loadAfterTimeSync(playlistRequestUri);
}
};
SntpClient.initialize(mediaPlaylistLoader, callback);
} else {
loadAfterTimeSync(playlistRequestUri);
}
}

private void loadAfterTimeSync(Uri playlistRequestUri) {
ParsingLoadable.Parser<HlsPlaylist> mediaPlaylistParser =
playlistParserFactory.createPlaylistParser(multivariantPlaylist, playlistSnapshot);
ParsingLoadable<HlsPlaylist> mediaPlaylistLoadable =
Expand Down