mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 04:52:39 +02:00
Further work.
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
package com.futo.platformplayer.sabr;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class ITagUtils {
|
||||||
|
public final static String AUDIO_68K_WEBM = "249";
|
||||||
|
public final static String AUDIO_89K_WEBM = "250";
|
||||||
|
public final static String AUDIO_133K_WEBM = "171";
|
||||||
|
public final static String AUDIO_156K_WEBM = "251";
|
||||||
|
public final static String AUDIO_48K_AAC = "139";
|
||||||
|
public final static String AUDIO_128K_AAC = "140";
|
||||||
|
public final static String VIDEO_144P_WEBM = "278";
|
||||||
|
public final static String VIDEO_144P_AVC = "160";
|
||||||
|
public final static String VIDEO_240P_WEBM = "242";
|
||||||
|
public final static String VIDEO_240P_AVC = "133";
|
||||||
|
public final static String VIDEO_360P_WEBM = "243";
|
||||||
|
public final static String VIDEO_360P_AVC = "134";
|
||||||
|
public final static String VIDEO_480P_WEBM = "244";
|
||||||
|
public final static String VIDEO_480P_AVC = "135";
|
||||||
|
public final static String VIDEO_720P_WEBM = "247";
|
||||||
|
public final static String VIDEO_720P_WEBM_60FPS_HDR = "334";
|
||||||
|
public final static String VIDEO_720P_AVC = "136";
|
||||||
|
public final static String VIDEO_720P_AVC_60FPS = "298";
|
||||||
|
public final static String VIDEO_1080P_WEBM = "248";
|
||||||
|
public final static String VIDEO_1080P_WEBM_60FPS_HDR = "335";
|
||||||
|
public final static String VIDEO_1080P_AVC = "137";
|
||||||
|
public final static String VIDEO_1080P_AVC_60FPS = "299";
|
||||||
|
public final static String VIDEO_1440P_WEBM = "271";
|
||||||
|
public final static String VIDEO_1440P_WEBM_60FPS_HDR = "336";
|
||||||
|
public final static String VIDEO_1440P_WEBM_60FPS = "308";
|
||||||
|
public final static String VIDEO_1440P_AVC = "264";
|
||||||
|
public final static String VIDEO_2160P_WEBM = "313";
|
||||||
|
public final static String VIDEO_2160P_WEBM_60FPS_HDR = "337";
|
||||||
|
public final static String VIDEO_2160P_WEBM_60FPS = "315";
|
||||||
|
public final static String VIDEO_2160P_AVC = "266";
|
||||||
|
public final static String VIDEO_2160P_AVC_HQ = "138";
|
||||||
|
|
||||||
|
public final static String MUXED_360P_WEBM = "43";
|
||||||
|
public final static String MUXED_360P_AVC = "18";
|
||||||
|
public final static String MUXED_720P_AVC = "22";
|
||||||
|
|
||||||
|
private final static List<String> sOrderedITagsAVC = Arrays.asList(
|
||||||
|
MUXED_360P_AVC, MUXED_720P_AVC,
|
||||||
|
AUDIO_48K_AAC, AUDIO_128K_AAC,
|
||||||
|
VIDEO_144P_AVC, VIDEO_240P_AVC,
|
||||||
|
VIDEO_360P_AVC, VIDEO_480P_AVC, VIDEO_720P_AVC, VIDEO_720P_AVC_60FPS,
|
||||||
|
VIDEO_1080P_AVC, VIDEO_1080P_AVC_60FPS, VIDEO_1440P_AVC, VIDEO_2160P_AVC, VIDEO_2160P_AVC_HQ);
|
||||||
|
|
||||||
|
private final static List<String> sOrderedITagsWEBM = Arrays.asList(
|
||||||
|
MUXED_360P_WEBM,
|
||||||
|
AUDIO_68K_WEBM, AUDIO_89K_WEBM, AUDIO_133K_WEBM, AUDIO_156K_WEBM,
|
||||||
|
VIDEO_144P_WEBM, VIDEO_240P_WEBM,
|
||||||
|
VIDEO_360P_WEBM, VIDEO_480P_WEBM, VIDEO_720P_WEBM, VIDEO_720P_WEBM_60FPS_HDR,
|
||||||
|
VIDEO_1080P_WEBM, VIDEO_1080P_WEBM_60FPS_HDR, VIDEO_1440P_WEBM, VIDEO_1440P_WEBM_60FPS_HDR, VIDEO_1440P_WEBM_60FPS,
|
||||||
|
VIDEO_2160P_WEBM, VIDEO_2160P_WEBM_60FPS_HDR, VIDEO_2160P_WEBM_60FPS);
|
||||||
|
|
||||||
|
private final static List<List<String>> sITagsContainer = Arrays.asList(sOrderedITagsAVC, sOrderedITagsWEBM);
|
||||||
|
public static final String AVC = "AVC";
|
||||||
|
public static final String WEBM = "VP9";
|
||||||
|
|
||||||
|
public static int compare(String leftITag, String rightITag) {
|
||||||
|
for (List<String> iTags : sITagsContainer) {
|
||||||
|
int left = iTags.indexOf(leftITag);
|
||||||
|
int right = iTags.indexOf(rightITag);
|
||||||
|
if (left != -1 && right != -1) {
|
||||||
|
return left - right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we can't be here
|
||||||
|
return 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean belongsToType(String type, String iTag) {
|
||||||
|
String realType = getRealType(iTag);
|
||||||
|
return type.equals(realType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean belongsToType(String type, int iTag) {
|
||||||
|
String realType = getRealType(String.valueOf(iTag));
|
||||||
|
return type.equals(realType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getRealType(String iTag) {
|
||||||
|
if (sOrderedITagsAVC.contains(iTag)) {
|
||||||
|
return AVC;
|
||||||
|
}
|
||||||
|
return WEBM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getAudioRateByTag(String iTag) {
|
||||||
|
switch (iTag) {
|
||||||
|
case AUDIO_128K_AAC:
|
||||||
|
return "44100";
|
||||||
|
case AUDIO_48K_AAC:
|
||||||
|
return "22050";
|
||||||
|
case AUDIO_156K_WEBM:
|
||||||
|
return "48000";
|
||||||
|
case AUDIO_133K_WEBM:
|
||||||
|
return "44100";
|
||||||
|
case AUDIO_89K_WEBM:
|
||||||
|
return "48000";
|
||||||
|
case AUDIO_68K_WEBM:
|
||||||
|
return "48000";
|
||||||
|
}
|
||||||
|
return "44100";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.futo.platformplayer.sabr;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface MediaFormat extends Comparable<MediaFormat> {
|
||||||
|
int FORMAT_TYPE_DASH = 0;
|
||||||
|
int FORMAT_TYPE_REGULAR = 1;
|
||||||
|
int FORMAT_TYPE_SABR = 2;
|
||||||
|
// Common
|
||||||
|
int getFormatType();
|
||||||
|
String getUrl();
|
||||||
|
String getMimeType();
|
||||||
|
String getITag();
|
||||||
|
boolean isDrc();
|
||||||
|
|
||||||
|
// DASH
|
||||||
|
String getClen();
|
||||||
|
String getBitrate();
|
||||||
|
String getProjectionType();
|
||||||
|
String getXtags();
|
||||||
|
int getWidth();
|
||||||
|
int getHeight();
|
||||||
|
String getIndex();
|
||||||
|
String getInit();
|
||||||
|
String getFps();
|
||||||
|
String getLmt();
|
||||||
|
String getQualityLabel();
|
||||||
|
String getFormat();
|
||||||
|
boolean isOtf();
|
||||||
|
String getOtfInitUrl();
|
||||||
|
String getOtfTemplateUrl();
|
||||||
|
String getLanguage();
|
||||||
|
// DASH LIVE
|
||||||
|
String getTargetDurationSec();
|
||||||
|
String getLastModified();
|
||||||
|
String getMaxDvrDurationSec();
|
||||||
|
|
||||||
|
// Other/Regular
|
||||||
|
String getQuality();
|
||||||
|
String getSignature();
|
||||||
|
String getAudioSamplingRate();
|
||||||
|
String getSourceUrl();
|
||||||
|
List<String> getSegmentUrlList();
|
||||||
|
List<String> getGlobalSegmentList();
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.futo.platformplayer.sabr;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
public class MediaFormatComparator implements Comparator<MediaFormat> {
|
||||||
|
public static final int ORDER_DESCENDANT = 0;
|
||||||
|
public static final int ORDER_ASCENDANT = 1;
|
||||||
|
private int mOrderType = ORDER_DESCENDANT;
|
||||||
|
|
||||||
|
public MediaFormatComparator() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public MediaFormatComparator(int orderType) {
|
||||||
|
mOrderType = orderType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: Descendant sorting (better on top). High quality playback on external player.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compare(MediaFormat leftItem, MediaFormat rightItem) {
|
||||||
|
if (leftItem.getGlobalSegmentList() != null ||
|
||||||
|
rightItem.getGlobalSegmentList() != null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mOrderType == ORDER_ASCENDANT) {
|
||||||
|
MediaFormat tmpItem = leftItem;
|
||||||
|
leftItem = rightItem;
|
||||||
|
rightItem = tmpItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
int leftItemBitrate = leftItem.getBitrate() == null ? 0 : parseInt(leftItem.getBitrate());
|
||||||
|
int rightItemBitrate = rightItem.getBitrate() == null ? 0 : parseInt(rightItem.getBitrate());
|
||||||
|
|
||||||
|
int leftItemHeight = leftItem.getHeight();
|
||||||
|
int rightItemHeight = rightItem.getHeight();
|
||||||
|
|
||||||
|
int delta = rightItemHeight - leftItemHeight;
|
||||||
|
|
||||||
|
if (delta == 0) {
|
||||||
|
delta = rightItemBitrate - leftItemBitrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNumeric(String s) {
|
||||||
|
return s != null && s.matches("^[-+]?\\d*\\.?\\d+$");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseInt(String num) {
|
||||||
|
if (!isNumeric(num)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(num);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.futo.platformplayer.sabr;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class MediaFormatUtils {
|
||||||
|
public static final String MIME_WEBM_AUDIO = "audio/webm";
|
||||||
|
public static final String MIME_WEBM_VIDEO = "video/webm";
|
||||||
|
public static final String MIME_MP4_AUDIO = "audio/mp4";
|
||||||
|
public static final String MIME_MP4_VIDEO = "video/mp4";
|
||||||
|
private static final Pattern CODECS_PATTERN = Pattern.compile(".*codecs=\\\"(.*)\\\"");
|
||||||
|
|
||||||
|
public static boolean isNumeric(String s) {
|
||||||
|
return s != null && s.matches("^[-+]?\\d*\\.?\\d+$");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDash(String id) {
|
||||||
|
if (!isNumeric(id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int maxRegularITag = 50;
|
||||||
|
int itag = Integer.parseInt(id);
|
||||||
|
|
||||||
|
return itag > maxRegularITag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDash(MediaFormat format) {
|
||||||
|
if (format.getITag() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format.getGlobalSegmentList() != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = format.getITag();
|
||||||
|
|
||||||
|
return isDash(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean checkMediaUrl(MediaFormat format) {
|
||||||
|
return format != null && format.getUrl() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String extractMimeType(MediaFormat format) {
|
||||||
|
if (format.getGlobalSegmentList() != null) {
|
||||||
|
return format.getMimeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
String codecs = extractCodecs(format);
|
||||||
|
|
||||||
|
if (codecs.startsWith("vorbis") ||
|
||||||
|
codecs.startsWith("opus")) {
|
||||||
|
return MIME_WEBM_AUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecs.startsWith("vp9") ||
|
||||||
|
codecs.startsWith("vp09")) {
|
||||||
|
return MIME_WEBM_VIDEO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecs.startsWith("mp4a") ||
|
||||||
|
codecs.startsWith("ec-3") ||
|
||||||
|
codecs.startsWith("ac-3")) {
|
||||||
|
return MIME_MP4_AUDIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codecs.startsWith("avc") ||
|
||||||
|
codecs.startsWith("av01")) {
|
||||||
|
return MIME_MP4_VIDEO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String extractCodecs(MediaFormat format) {
|
||||||
|
// input example: video/mp4;+codecs="avc1.640033"
|
||||||
|
Matcher matcher = CODECS_PATTERN.matcher(format.getMimeType());
|
||||||
|
matcher.find();
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLiveMedia(MediaFormat format) {
|
||||||
|
boolean isLive =
|
||||||
|
format.getUrl().contains("live=1") ||
|
||||||
|
format.getUrl().contains("yt_live_broadcast");
|
||||||
|
|
||||||
|
return isLive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalize(String word) {
|
||||||
|
if (word == null || word.isEmpty()) {
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
return word.toLowerCase().replace("ё", "е");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean startsWith(String word, String prefix) {
|
||||||
|
if (word == null && prefix == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (word == null || prefix == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
word = normalize(word);
|
||||||
|
prefix = normalize(prefix);
|
||||||
|
|
||||||
|
return word.startsWith(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAudio(String mimeType) {
|
||||||
|
return startsWith(mimeType, "audio");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isVideo(String mimeType) {
|
||||||
|
return startsWith(mimeType, "video");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.futo.platformplayer.sabr;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface MediaItemFormatInfo {
|
||||||
|
List<MediaFormat> getAdaptiveFormats();
|
||||||
|
List<MediaFormat> getUrlFormats();
|
||||||
|
List<MediaSubtitle> getSubtitles();
|
||||||
|
String getHlsManifestUrl();
|
||||||
|
String getDashManifestUrl();
|
||||||
|
// video metadata
|
||||||
|
String getLengthSeconds();
|
||||||
|
String getTitle();
|
||||||
|
String getAuthor();
|
||||||
|
String getViewCount();
|
||||||
|
String getDescription();
|
||||||
|
String getVideoId();
|
||||||
|
String getChannelId();
|
||||||
|
boolean isLive();
|
||||||
|
boolean isLiveContent();
|
||||||
|
boolean containsMedia();
|
||||||
|
boolean containsSabrFormats();
|
||||||
|
boolean containsDashFormats();
|
||||||
|
boolean containsHlsUrl();
|
||||||
|
boolean containsDashUrl();
|
||||||
|
boolean containsUrlFormats();
|
||||||
|
boolean hasExtendedHlsFormats();
|
||||||
|
float getVolumeLevel();
|
||||||
|
InputStream createMpdStream();
|
||||||
|
//Observable<InputStream> createMpdStreamObservable();
|
||||||
|
List<String> createUrlList();
|
||||||
|
MediaItemStoryboard createStoryboard();
|
||||||
|
boolean isUnplayable();
|
||||||
|
boolean isUnknownError();
|
||||||
|
String getPlayabilityStatus();
|
||||||
|
boolean isStreamSeekable();
|
||||||
|
/**
|
||||||
|
* Stream start time in UTC (!!!).<br/>
|
||||||
|
* E.g.: <b>2021-10-06T13:36:25+00:00</b>
|
||||||
|
*/
|
||||||
|
String getStartTimestamp();
|
||||||
|
String getUploadDate();
|
||||||
|
/**
|
||||||
|
* Stream start time in UNIX format.<br/>
|
||||||
|
*/
|
||||||
|
long getStartTimeMs();
|
||||||
|
/**
|
||||||
|
* Number of the stream first segment
|
||||||
|
*/
|
||||||
|
int getStartSegmentNum();
|
||||||
|
/**
|
||||||
|
* Precise segment duration.<br/>
|
||||||
|
* Used inside live streams
|
||||||
|
*/
|
||||||
|
int getSegmentDurationUs();
|
||||||
|
String getPaidContentText();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.futo.platformplayer.sabr;
|
||||||
|
|
||||||
|
public interface MediaItemStoryboard {
|
||||||
|
int getGroupDurationMS();
|
||||||
|
Size getGroupSize();
|
||||||
|
String getGroupUrl(int imgNum);
|
||||||
|
interface Size {
|
||||||
|
int getDurationEachMS();
|
||||||
|
int getStartNum();
|
||||||
|
int getWidth();
|
||||||
|
int getHeight();
|
||||||
|
int getRowCount();
|
||||||
|
int getColCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.futo.platformplayer.sabr;
|
||||||
|
|
||||||
|
public interface MediaSubtitle {
|
||||||
|
String getBaseUrl();
|
||||||
|
void setBaseUrl(String baseUrl);
|
||||||
|
boolean isTranslatable();
|
||||||
|
void setTranslatable(boolean translatable);
|
||||||
|
String getLanguageCode();
|
||||||
|
void setLanguageCode(String languageCode);
|
||||||
|
String getVssId();
|
||||||
|
void setVssId(String vssId);
|
||||||
|
String getName();
|
||||||
|
void setName(String name);
|
||||||
|
String getMimeType();
|
||||||
|
void setMimeType(String mimeType);
|
||||||
|
String getCodecs();
|
||||||
|
void setCodecs(String codecs);
|
||||||
|
String getType();
|
||||||
|
void setType(String type);
|
||||||
|
}
|
||||||
@@ -9,7 +9,10 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.exoplayer.LoadingInfo;
|
||||||
import androidx.media3.exoplayer.SeekParameters;
|
import androidx.media3.exoplayer.SeekParameters;
|
||||||
|
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
||||||
|
import androidx.media3.exoplayer.drm.DrmSessionManager;
|
||||||
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
||||||
import androidx.media3.exoplayer.source.EmptySampleStream;
|
import androidx.media3.exoplayer.source.EmptySampleStream;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
@@ -72,6 +75,8 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
private int periodIndex;
|
private int periodIndex;
|
||||||
private List<EventStream> eventStreams;
|
private List<EventStream> eventStreams;
|
||||||
private boolean notifiedReadingStarted;
|
private boolean notifiedReadingStarted;
|
||||||
|
private final DrmSessionManager drmSessionManager;
|
||||||
|
private final DrmSessionEventListener.EventDispatcher drmEventDispatcher;
|
||||||
|
|
||||||
public SabrMediaPeriod(
|
public SabrMediaPeriod(
|
||||||
int id,
|
int id,
|
||||||
@@ -107,6 +112,13 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
Pair<TrackGroupArray, TrackGroupInfo[]> result = buildTrackGroups(period.adaptationSets);
|
Pair<TrackGroupArray, TrackGroupInfo[]> result = buildTrackGroups(period.adaptationSets);
|
||||||
trackGroups = result.first;
|
trackGroups = result.first;
|
||||||
trackGroupInfos = result.second;
|
trackGroupInfos = result.second;
|
||||||
|
this.drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
|
||||||
|
this.drmEventDispatcher = new DrmSessionEventListener.EventDispatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLoading() {
|
||||||
|
return compositeSequenceableLoader.isLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -170,7 +182,6 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
@Override
|
@Override
|
||||||
public long readDiscontinuity() {
|
public long readDiscontinuity() {
|
||||||
if (!notifiedReadingStarted) {
|
if (!notifiedReadingStarted) {
|
||||||
eventDispatcher.readingStarted();
|
|
||||||
notifiedReadingStarted = true;
|
notifiedReadingStarted = true;
|
||||||
}
|
}
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
@@ -208,8 +219,8 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean continueLoading(long positionUs) {
|
public boolean continueLoading(LoadingInfo loadingInfo) {
|
||||||
return compositeSequenceableLoader.continueLoading(positionUs);
|
return compositeSequenceableLoader.continueLoading(loadingInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -255,9 +266,9 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
sampleStream.release(this);
|
sampleStream.release(this);
|
||||||
}
|
}
|
||||||
callback = null;
|
callback = null;
|
||||||
eventDispatcher.mediaPeriodReleased();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static ChunkSampleStream<SabrChunkSource>[] newSampleStreamArray(int length) {
|
private static ChunkSampleStream<SabrChunkSource>[] newSampleStreamArray(int length) {
|
||||||
return new ChunkSampleStream[length];
|
return new ChunkSampleStream[length];
|
||||||
@@ -385,8 +396,11 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
eventMessageTrackGroupIndex,
|
eventMessageTrackGroupIndex,
|
||||||
cea608TrackGroupIndex);
|
cea608TrackGroupIndex);
|
||||||
if (eventMessageTrackGroupIndex != C.INDEX_UNSET) {
|
if (eventMessageTrackGroupIndex != C.INDEX_UNSET) {
|
||||||
Format format = Format.createSampleFormat(firstAdaptationSet.id + ":emsg",
|
Format format =
|
||||||
MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null);
|
new Format.Builder()
|
||||||
|
.setId(firstAdaptationSet.id + ":emsg")
|
||||||
|
.setSampleMimeType(MimeTypes.APPLICATION_EMSG)
|
||||||
|
.build();
|
||||||
trackGroups[eventMessageTrackGroupIndex] = new TrackGroup(format);
|
trackGroups[eventMessageTrackGroupIndex] = new TrackGroup(format);
|
||||||
trackGroupInfos[eventMessageTrackGroupIndex] =
|
trackGroupInfos[eventMessageTrackGroupIndex] =
|
||||||
TrackGroupInfo.embeddedEmsgTrack(adaptationSetIndices, primaryTrackGroupIndex);
|
TrackGroupInfo.embeddedEmsgTrack(adaptationSetIndices, primaryTrackGroupIndex);
|
||||||
@@ -526,7 +540,11 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChunkSampleStream<SabrChunkSource> buildSampleStream(TrackGroupInfo trackGroupInfo, ExoTrackSelection selection, long positionUs) {
|
private ChunkSampleStream<SabrChunkSource> buildSampleStream(
|
||||||
|
TrackGroupInfo trackGroupInfo,
|
||||||
|
ExoTrackSelection selection,
|
||||||
|
long positionUs) {
|
||||||
|
|
||||||
int embeddedTrackCount = 0;
|
int embeddedTrackCount = 0;
|
||||||
boolean enableEventMessageTrack =
|
boolean enableEventMessageTrack =
|
||||||
trackGroupInfo.embeddedEventMessageTrackGroupIndex != C.INDEX_UNSET;
|
trackGroupInfo.embeddedEventMessageTrackGroupIndex != C.INDEX_UNSET;
|
||||||
@@ -536,34 +554,72 @@ final class SabrMediaPeriod implements MediaPeriod, SequenceableLoader.Callback<
|
|||||||
trackGroups.get(trackGroupInfo.embeddedEventMessageTrackGroupIndex);
|
trackGroups.get(trackGroupInfo.embeddedEventMessageTrackGroupIndex);
|
||||||
embeddedTrackCount++;
|
embeddedTrackCount++;
|
||||||
}
|
}
|
||||||
boolean enableCea608Tracks = trackGroupInfo.embeddedCea608TrackGroupIndex != C.INDEX_UNSET;
|
boolean enableCea608Tracks =
|
||||||
|
trackGroupInfo.embeddedCea608TrackGroupIndex != C.INDEX_UNSET;
|
||||||
TrackGroup embeddedCea608TrackGroup = null;
|
TrackGroup embeddedCea608TrackGroup = null;
|
||||||
if (enableCea608Tracks) {
|
if (enableCea608Tracks) {
|
||||||
embeddedCea608TrackGroup = trackGroups.get(trackGroupInfo.embeddedCea608TrackGroupIndex);
|
embeddedCea608TrackGroup =
|
||||||
|
trackGroups.get(trackGroupInfo.embeddedCea608TrackGroupIndex);
|
||||||
embeddedTrackCount += embeddedCea608TrackGroup.length;
|
embeddedTrackCount += embeddedCea608TrackGroup.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
Format[] embeddedTrackFormats = new Format[embeddedTrackCount];
|
Format[] embeddedTrackFormats = new Format[embeddedTrackCount];
|
||||||
int[] embeddedTrackTypes = new int[embeddedTrackCount];
|
int[] embeddedTrackTypes = new int[embeddedTrackCount];
|
||||||
embeddedTrackCount = 0;
|
embeddedTrackCount = 0;
|
||||||
|
|
||||||
if (enableEventMessageTrack) {
|
if (enableEventMessageTrack) {
|
||||||
embeddedTrackFormats[embeddedTrackCount] = embeddedEventMessageTrackGroup.getFormat(0);
|
embeddedTrackFormats[embeddedTrackCount] =
|
||||||
|
embeddedEventMessageTrackGroup.getFormat(0);
|
||||||
embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_METADATA;
|
embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_METADATA;
|
||||||
embeddedTrackCount++;
|
embeddedTrackCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Format> embeddedCea608TrackFormats = new ArrayList<>();
|
List<Format> embeddedCea608TrackFormats = new ArrayList<>();
|
||||||
if (enableCea608Tracks) {
|
if (enableCea608Tracks) {
|
||||||
for (int i = 0; i < embeddedCea608TrackGroup.length; i++) {
|
for (int i = 0; i < embeddedCea608TrackGroup.length; i++) {
|
||||||
embeddedTrackFormats[embeddedTrackCount] = embeddedCea608TrackGroup.getFormat(i);
|
embeddedTrackFormats[embeddedTrackCount] =
|
||||||
|
embeddedCea608TrackGroup.getFormat(i);
|
||||||
embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_TEXT;
|
embeddedTrackTypes[embeddedTrackCount] = C.TRACK_TYPE_TEXT;
|
||||||
embeddedCea608TrackFormats.add(embeddedTrackFormats[embeddedTrackCount]);
|
embeddedCea608TrackFormats.add(embeddedTrackFormats[embeddedTrackCount]);
|
||||||
embeddedTrackCount++;
|
embeddedTrackCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerTrackEmsgHandler trackPlayerEmsgHandler = manifest.dynamic && enableEventMessageTrack ? playerEmsgHandler.newPlayerTrackEmsgHandler() : null;
|
PlayerTrackEmsgHandler trackPlayerEmsgHandler =
|
||||||
SabrChunkSource chunkSource = chunkSourceFactory.createSabrChunkSource(manifestLoaderErrorThrower, manifest, periodIndex, trackGroupInfo.adaptationSetIndices, selection, trackGroupInfo.trackType, elapsedRealtimeOffsetMs, enableEventMessageTrack, embeddedCea608TrackFormats, trackPlayerEmsgHandler, transferListener);
|
manifest.dynamic && enableEventMessageTrack
|
||||||
ChunkSampleStream<SabrChunkSource> stream = new ChunkSampleStream<>(trackGroupInfo.trackType, embeddedTrackTypes, embeddedTrackFormats, chunkSource, this, allocator, positionUs, loadErrorHandlingPolicy, eventDispatcher);
|
? playerEmsgHandler.newPlayerTrackEmsgHandler()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
SabrChunkSource chunkSource =
|
||||||
|
chunkSourceFactory.createSabrChunkSource(
|
||||||
|
manifestLoaderErrorThrower,
|
||||||
|
manifest,
|
||||||
|
periodIndex,
|
||||||
|
trackGroupInfo.adaptationSetIndices,
|
||||||
|
selection,
|
||||||
|
trackGroupInfo.trackType,
|
||||||
|
elapsedRealtimeOffsetMs,
|
||||||
|
enableEventMessageTrack,
|
||||||
|
embeddedCea608TrackFormats,
|
||||||
|
trackPlayerEmsgHandler,
|
||||||
|
transferListener);
|
||||||
|
|
||||||
|
ChunkSampleStream<SabrChunkSource> stream =
|
||||||
|
new ChunkSampleStream<>(
|
||||||
|
trackGroupInfo.trackType,
|
||||||
|
embeddedTrackTypes,
|
||||||
|
embeddedTrackFormats,
|
||||||
|
chunkSource,
|
||||||
|
/* callback= */ this,
|
||||||
|
allocator,
|
||||||
|
positionUs,
|
||||||
|
drmSessionManager,
|
||||||
|
drmEventDispatcher,
|
||||||
|
loadErrorHandlingPolicy,
|
||||||
|
eventDispatcher,
|
||||||
|
/* canReportInitialDiscontinuity= */ true,
|
||||||
|
/* downloadExecutor= */ null);
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
// The map is also accessed on the loading thread so synchronize access.
|
// The map is also accessed on the loading thread so synchronize access.
|
||||||
trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler);
|
trackEmsgHandlerBySampleStream.put(stream, trackPlayerEmsgHandler);
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import android.util.SparseArray;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider;
|
||||||
import androidx.media3.exoplayer.source.BaseMediaSource;
|
import androidx.media3.exoplayer.source.BaseMediaSource;
|
||||||
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
import androidx.media3.exoplayer.source.CompositeSequenceableLoaderFactory;
|
||||||
import androidx.media3.exoplayer.source.DefaultCompositeSequenceableLoaderFactory;
|
import androidx.media3.exoplayer.source.DefaultCompositeSequenceableLoaderFactory;
|
||||||
@@ -34,17 +36,14 @@ import java.io.IOException;
|
|||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class SabrMediaSource extends BaseMediaSource {
|
public final class SabrMediaSource extends BaseMediaSource {
|
||||||
/**
|
|
||||||
* The interval in milliseconds between invocations of {@link
|
|
||||||
* SourceInfoRefreshListener#onSourceInfoRefreshed(MediaSource, Timeline, Object)} when the
|
|
||||||
* source's {@link Timeline} is changing dynamically (for example, for incomplete live streams).
|
|
||||||
*/
|
|
||||||
private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000;
|
private static final int NOTIFY_MANIFEST_INTERVAL_MS = 5000;
|
||||||
/**
|
/**
|
||||||
* The minimum default start position for live streams, relative to the start of the live window.
|
* The minimum default start position for live streams, relative to the start of the live window.
|
||||||
*/
|
*/
|
||||||
private static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5000000;
|
private static final long MIN_LIVE_DEFAULT_START_POSITION_US = 5000000;
|
||||||
private final SabrManifest manifest;
|
private final SabrManifest manifest;
|
||||||
|
private final MediaItem mediaItem;
|
||||||
private final SabrChunkSource.Factory chunkSourceFactory;
|
private final SabrChunkSource.Factory chunkSourceFactory;
|
||||||
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
@@ -68,6 +67,7 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
|
|
||||||
private SabrMediaSource(
|
private SabrMediaSource(
|
||||||
SabrManifest manifest,
|
SabrManifest manifest,
|
||||||
|
MediaItem mediaItem,
|
||||||
SabrChunkSource.Factory chunkSourceFactory,
|
SabrChunkSource.Factory chunkSourceFactory,
|
||||||
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
|
||||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||||
@@ -76,6 +76,7 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
@Nullable Object tag
|
@Nullable Object tag
|
||||||
) {
|
) {
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
|
this.mediaItem = mediaItem;
|
||||||
this.chunkSourceFactory = chunkSourceFactory;
|
this.chunkSourceFactory = chunkSourceFactory;
|
||||||
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
|
||||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||||
@@ -87,6 +88,11 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
manifestLoadErrorThrower = new ManifestLoadErrorThrower();
|
manifestLoadErrorThrower = new ManifestLoadErrorThrower();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaItem getMediaItem() {
|
||||||
|
return mediaItem;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
this.mediaTransferListener = mediaTransferListener;
|
this.mediaTransferListener = mediaTransferListener;
|
||||||
@@ -217,7 +223,7 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
windowDefaultStartPositionUs,
|
windowDefaultStartPositionUs,
|
||||||
manifest,
|
manifest,
|
||||||
tag);
|
tag);
|
||||||
refreshSourceInfo(timeline, manifest);
|
refreshSourceInfo(timeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
private long getNowUnixTimeUs() {
|
private long getNowUnixTimeUs() {
|
||||||
@@ -231,8 +237,10 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
public static final class Factory implements MediaSource.Factory {
|
public static final class Factory implements MediaSource.Factory {
|
||||||
private final SabrChunkSource.Factory chunkSourceFactory;
|
private final SabrChunkSource.Factory chunkSourceFactory;
|
||||||
@Nullable private final DataSource.Factory manifestDataSourceFactory;
|
@Nullable private final DataSource.Factory manifestDataSourceFactory;
|
||||||
private final DefaultLoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private final DefaultCompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private final DefaultCompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
|
@Nullable private DrmSessionManagerProvider drmSessionManagerProvider;
|
||||||
|
|
||||||
private long livePresentationDelayMs;
|
private long livePresentationDelayMs;
|
||||||
private boolean livePresentationDelayOverridesManifest;
|
private boolean livePresentationDelayOverridesManifest;
|
||||||
private boolean isCreateCalled;
|
private boolean isCreateCalled;
|
||||||
@@ -258,8 +266,35 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaSource createMediaSource(Uri uri) {
|
public Factory setDrmSessionManagerProvider(DrmSessionManagerProvider drmSessionManagerProvider) {
|
||||||
return null;
|
Assertions.checkState(!isCreateCalled);
|
||||||
|
this.drmSessionManagerProvider = drmSessionManagerProvider;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SabrMediaSource createMediaSource(MediaItem mediaItem) {
|
||||||
|
Assertions.checkNotNull(mediaItem);
|
||||||
|
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
||||||
|
Assertions.checkNotNull(localConfiguration, "MediaItem must have a local configuration");
|
||||||
|
Object localTag = localConfiguration.tag;
|
||||||
|
Assertions.checkArgument(
|
||||||
|
localTag instanceof SabrManifest,
|
||||||
|
"MediaItem.localConfiguration.tag must be a SabrManifest"
|
||||||
|
);
|
||||||
|
SabrManifest manifest = (SabrManifest) localTag;
|
||||||
|
|
||||||
|
isCreateCalled = true;
|
||||||
|
return new SabrMediaSource(
|
||||||
|
manifest,
|
||||||
|
mediaItem,
|
||||||
|
chunkSourceFactory,
|
||||||
|
compositeSequenceableLoaderFactory,
|
||||||
|
loadErrorHandlingPolicy,
|
||||||
|
livePresentationDelayMs,
|
||||||
|
livePresentationDelayOverridesManifest,
|
||||||
|
tag
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,8 +306,15 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
*/
|
*/
|
||||||
public SabrMediaSource createMediaSource(SabrManifest manifest) {
|
public SabrMediaSource createMediaSource(SabrManifest manifest) {
|
||||||
isCreateCalled = true;
|
isCreateCalled = true;
|
||||||
|
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder()
|
||||||
|
.setMediaId("sabr:" + manifest.hashCode())
|
||||||
|
.setTag(manifest)
|
||||||
|
.build();
|
||||||
|
|
||||||
return new SabrMediaSource(
|
return new SabrMediaSource(
|
||||||
manifest,
|
manifest,
|
||||||
|
mediaItem,
|
||||||
chunkSourceFactory,
|
chunkSourceFactory,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
@@ -301,7 +343,7 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getSupportedTypes() {
|
public int[] getSupportedTypes() {
|
||||||
return new int[0];
|
return new int[] { C.CONTENT_TYPE_OTHER };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -314,9 +356,10 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
* @return This factory, for convenience.
|
* @return This factory, for convenience.
|
||||||
* @throws IllegalStateException If one of the {@code create} methods has already been called.
|
* @throws IllegalStateException If one of the {@code create} methods has already been called.
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
public Factory setLoadErrorHandlingPolicy(LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
||||||
//Assertions.checkState(!isCreateCalled);
|
Assertions.checkState(!isCreateCalled);
|
||||||
//this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,15 +381,6 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
return setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount));
|
return setLoadErrorHandlingPolicy(new DefaultLoadErrorHandlingPolicy(minLoadableRetryCount));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a tag for the media source which will be published in the {@link
|
|
||||||
* androidx.media3.Timeline} of the source as {@link
|
|
||||||
* androidx.media3.Timeline.Window#tag}.
|
|
||||||
*
|
|
||||||
* @param tag A tag for the media source.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
* @throws IllegalStateException If one of the {@code create} methods has already been called.
|
|
||||||
*/
|
|
||||||
public Factory setTag(Object tag) {
|
public Factory setTag(Object tag) {
|
||||||
Assertions.checkState(!isCreateCalled);
|
Assertions.checkState(!isCreateCalled);
|
||||||
this.tag = tag;
|
this.tag = tag;
|
||||||
@@ -466,26 +500,32 @@ public final class SabrMediaSource extends BaseMediaSource {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Window getWindow(
|
public Window getWindow(
|
||||||
int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
|
int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||||
Assertions.checkIndex(windowIndex, 0, 1);
|
Assertions.checkIndex(windowIndex, 0, 1);
|
||||||
long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs(
|
|
||||||
defaultPositionProjectionUs);
|
long windowDefaultStartPositionUs =
|
||||||
Object tag = setTag ? windowTag : null;
|
getAdjustedWindowDefaultStartPositionUs(defaultPositionProjectionUs);
|
||||||
|
|
||||||
boolean isDynamic =
|
boolean isDynamic =
|
||||||
manifest.dynamic
|
manifest.dynamic
|
||||||
&& manifest.minUpdatePeriodMs != C.TIME_UNSET
|
&& manifest.minUpdatePeriodMs != C.TIME_UNSET
|
||||||
&& manifest.durationMs == C.TIME_UNSET;
|
&& manifest.durationMs == C.TIME_UNSET;
|
||||||
|
|
||||||
return window.set(
|
return window.set(
|
||||||
tag,
|
/* uid= */ Window.SINGLE_WINDOW_UID,
|
||||||
presentationStartTimeMs,
|
/* mediaItem= */ null,
|
||||||
windowStartTimeMs,
|
/* manifest= */ manifest,
|
||||||
|
/* presentationStartTimeMs= */ presentationStartTimeMs,
|
||||||
|
/* windowStartTimeMs= */ windowStartTimeMs,
|
||||||
|
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
|
||||||
/* isSeekable= */ true,
|
/* isSeekable= */ true,
|
||||||
isDynamic,
|
/* isDynamic= */ isDynamic,
|
||||||
windowDefaultStartPositionUs,
|
/* liveConfiguration= */ null,
|
||||||
windowDurationUs,
|
/* defaultPositionUs= */ windowDefaultStartPositionUs,
|
||||||
|
/* durationUs= */ windowDurationUs,
|
||||||
/* firstPeriodIndex= */ 0,
|
/* firstPeriodIndex= */ 0,
|
||||||
/* lastPeriodIndex= */ getPeriodCount() - 1,
|
/* lastPeriodIndex= */ getPeriodCount() - 1,
|
||||||
offsetInFirstPeriodUs);
|
/* positionInFirstPeriodUs= */ offsetInFirstPeriodUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
package com.futo.platformplayer.sabr.manifest;
|
package com.futo.platformplayer.sabr.manifest;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.DrmInitData;
|
import androidx.media3.common.DrmInitData;
|
||||||
import androidx.media3.common.DrmInitData.SchemeData;
|
import androidx.media3.common.DrmInitData.SchemeData;
|
||||||
|
|
||||||
|
import com.futo.platformplayer.sabr.ITagUtils;
|
||||||
|
import com.futo.platformplayer.sabr.MediaFormat;
|
||||||
|
import com.futo.platformplayer.sabr.MediaFormatComparator;
|
||||||
|
import com.futo.platformplayer.sabr.MediaFormatUtils;
|
||||||
|
import com.futo.platformplayer.sabr.MediaItemFormatInfo;
|
||||||
|
import com.futo.platformplayer.sabr.MediaSubtitle;
|
||||||
import com.futo.platformplayer.sabr.manifest.SegmentBase.SegmentList;
|
import com.futo.platformplayer.sabr.manifest.SegmentBase.SegmentList;
|
||||||
import com.futo.platformplayer.sabr.manifest.SegmentBase.SegmentTemplate;
|
import com.futo.platformplayer.sabr.manifest.SegmentBase.SegmentTemplate;
|
||||||
import com.futo.platformplayer.sabr.manifest.SegmentBase.SegmentTimelineElement;
|
import com.futo.platformplayer.sabr.manifest.SegmentBase.SegmentTimelineElement;
|
||||||
import com.futo.platformplayer.sabr.manifest.SegmentBase.SingleSegmentBase;
|
import com.futo.platformplayer.sabr.manifest.SegmentBase.SingleSegmentBase;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import com.liskovsoft.mediaserviceinterfaces.data.MediaFormat;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.liskovsoft.mediaserviceinterfaces.data.MediaItemFormatInfo;
|
|
||||||
import com.liskovsoft.mediaserviceinterfaces.data.MediaSubtitle;
|
|
||||||
import com.liskovsoft.sharedutils.helpers.Helpers;
|
|
||||||
import com.liskovsoft.sharedutils.mylogger.Log;
|
|
||||||
import com.liskovsoft.youtubeapi.formatbuilders.mpdbuilder.MediaFormatComparator;
|
|
||||||
import com.liskovsoft.youtubeapi.formatbuilders.utils.ITagUtils;
|
|
||||||
import com.liskovsoft.youtubeapi.formatbuilders.utils.MediaFormatUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -31,6 +34,7 @@ import java.util.Map;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
public class SabrManifestParser {
|
public class SabrManifestParser {
|
||||||
private static final String TAG = SabrManifestParser.class.getSimpleName();
|
private static final String TAG = SabrManifestParser.class.getSimpleName();
|
||||||
private int mId;
|
private int mId;
|
||||||
@@ -55,6 +59,50 @@ public class SabrManifestParser {
|
|||||||
return parseSabrManifest(formatInfo);
|
return parseSabrManifest(formatInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isInteger(String s) {
|
||||||
|
return s != null && s.matches("^[-+]?\\d+$");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isNumeric(String s) {
|
||||||
|
return s != null && s.matches("^[-+]?\\d*\\.?\\d+$");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int parseInt(String numString) {
|
||||||
|
return parseInt(numString, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int parseInt(String numString, int defaultValue) {
|
||||||
|
if (!isInteger(numString)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(numString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long parseLong(String numString) {
|
||||||
|
return parseLong(numString, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long parseLong(String numString, long defaultValue) {
|
||||||
|
if (!isInteger(numString)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Long.parseLong(numString);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float parseFloat(String numString) {
|
||||||
|
return parseFloat(numString, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float parseFloat(String numString, float defaultValue) {
|
||||||
|
if (!isNumeric(numString)) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Float.parseFloat(numString);
|
||||||
|
}
|
||||||
|
|
||||||
private SabrManifest parseSabrManifest(MediaItemFormatInfo formatInfo) {
|
private SabrManifest parseSabrManifest(MediaItemFormatInfo formatInfo) {
|
||||||
long availabilityStartTime = C.TIME_UNSET;
|
long availabilityStartTime = C.TIME_UNSET;
|
||||||
long durationMs = getDurationMs(formatInfo);
|
long durationMs = getDurationMs(formatInfo);
|
||||||
@@ -86,7 +134,7 @@ public class SabrManifestParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static long getDurationMs(MediaItemFormatInfo formatInfo) {
|
private static long getDurationMs(MediaItemFormatInfo formatInfo) {
|
||||||
long lenSeconds = Helpers.parseLong(formatInfo.getLengthSeconds());
|
long lenSeconds = parseLong(formatInfo.getLengthSeconds());
|
||||||
return lenSeconds > 0 ? lenSeconds * 1_000 : C.TIME_UNSET;
|
return lenSeconds > 0 ? lenSeconds * 1_000 : C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +332,7 @@ public class SabrManifestParser {
|
|||||||
|
|
||||||
// SegmentURL tag
|
// SegmentURL tag
|
||||||
for (String segment : format.getGlobalSegmentList()) {
|
for (String segment : format.getGlobalSegmentList()) {
|
||||||
long duration = Helpers.parseLong(segment, C.TIME_UNSET);
|
long duration = parseLong(segment, C.TIME_UNSET);
|
||||||
int count = 1;
|
int count = 1;
|
||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
timeline.add(new SegmentTimelineElement(elapsedTime, duration));
|
timeline.add(new SegmentTimelineElement(elapsedTime, duration));
|
||||||
@@ -347,14 +395,14 @@ public class SabrManifestParser {
|
|||||||
int roleFlags = C.ROLE_FLAG_MAIN;
|
int roleFlags = C.ROLE_FLAG_MAIN;
|
||||||
int selectionFlags = C.SELECTION_FLAG_DEFAULT;
|
int selectionFlags = C.SELECTION_FLAG_DEFAULT;
|
||||||
String id = mediaFormat.isDrc() ? mediaFormat.getITag() + "-drc" : mediaFormat.getITag();
|
String id = mediaFormat.isDrc() ? mediaFormat.getITag() + "-drc" : mediaFormat.getITag();
|
||||||
int bandwidth = Helpers.parseInt(mediaFormat.getBitrate(), Format.NO_VALUE);
|
int bandwidth = parseInt(mediaFormat.getBitrate(), Format.NO_VALUE);
|
||||||
String mimeType = MediaFormatUtils.extractMimeType(mediaFormat);
|
String mimeType = MediaFormatUtils.extractMimeType(mediaFormat);
|
||||||
String codecs = MediaFormatUtils.extractCodecs(mediaFormat);
|
String codecs = MediaFormatUtils.extractCodecs(mediaFormat);
|
||||||
int width = mediaFormat.getWidth();
|
int width = mediaFormat.getWidth();
|
||||||
int height = mediaFormat.getHeight();
|
int height = mediaFormat.getHeight();
|
||||||
float frameRate = Helpers.parseFloat(mediaFormat.getFps(), Format.NO_VALUE);
|
float frameRate = parseFloat(mediaFormat.getFps(), Format.NO_VALUE);
|
||||||
int audioChannels = Format.NO_VALUE;
|
int audioChannels = Format.NO_VALUE;
|
||||||
int audioSamplingRate = Helpers.parseInt(ITagUtils.getAudioRateByTag(mediaFormat.getITag()), Format.NO_VALUE);
|
int audioSamplingRate = parseInt(ITagUtils.getAudioRateByTag(mediaFormat.getITag()), Format.NO_VALUE);
|
||||||
String language = mediaFormat.getLanguage();
|
String language = mediaFormat.getLanguage();
|
||||||
String baseUrl = mediaFormat.getUrl();
|
String baseUrl = mediaFormat.getUrl();
|
||||||
String label = null;
|
String label = null;
|
||||||
@@ -432,22 +480,42 @@ public class SabrManifestParser {
|
|||||||
|
|
||||||
protected Representation buildRepresentation(
|
protected Representation buildRepresentation(
|
||||||
RepresentationInfo representationInfo,
|
RepresentationInfo representationInfo,
|
||||||
String label,
|
@Nullable String label,
|
||||||
String extraDrmSchemeType,
|
@Nullable String extraDrmSchemeType,
|
||||||
ArrayList<SchemeData> extraDrmSchemeDatas) {
|
ArrayList<SchemeData> extraDrmSchemeDatas) {
|
||||||
Format format = representationInfo.format;
|
|
||||||
|
// Start from the existing format
|
||||||
|
Format.Builder formatBuilder = representationInfo.format.buildUpon();
|
||||||
|
|
||||||
|
// copyWithLabel(label)
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
format = format.copyWithLabel(label);
|
formatBuilder.setLabel(label);
|
||||||
}
|
}
|
||||||
String drmSchemeType = representationInfo.drmSchemeType != null
|
|
||||||
? representationInfo.drmSchemeType : extraDrmSchemeType;
|
// Decide scheme type: representationInfo.drmSchemeType wins over extraDrmSchemeType
|
||||||
|
String drmSchemeType =
|
||||||
|
representationInfo.drmSchemeType != null
|
||||||
|
? representationInfo.drmSchemeType
|
||||||
|
: extraDrmSchemeType;
|
||||||
|
|
||||||
|
// Accumulate DRM scheme datas (same as your old code)
|
||||||
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
|
ArrayList<SchemeData> drmSchemeDatas = representationInfo.drmSchemeDatas;
|
||||||
|
if (extraDrmSchemeDatas != null && !extraDrmSchemeDatas.isEmpty()) {
|
||||||
drmSchemeDatas.addAll(extraDrmSchemeDatas);
|
drmSchemeDatas.addAll(extraDrmSchemeDatas);
|
||||||
|
}
|
||||||
|
|
||||||
if (!drmSchemeDatas.isEmpty()) {
|
if (!drmSchemeDatas.isEmpty()) {
|
||||||
filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
|
filterRedundantIncompleteSchemeDatas(drmSchemeDatas);
|
||||||
|
|
||||||
DrmInitData drmInitData = new DrmInitData(drmSchemeType, drmSchemeDatas);
|
DrmInitData drmInitData = new DrmInitData(drmSchemeType, drmSchemeDatas);
|
||||||
format = format.copyWithDrmInitData(drmInitData);
|
|
||||||
|
// copyWithDrmInitData(drmInitData)
|
||||||
|
formatBuilder.setDrmInitData(drmInitData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Format format = formatBuilder.build();
|
||||||
|
|
||||||
|
// Representation.newInstance(...) still exists with this signature in Media3.:contentReference[oaicite:1]{index=1}
|
||||||
return Representation.newInstance(
|
return Representation.newInstance(
|
||||||
representationInfo.revisionId,
|
representationInfo.revisionId,
|
||||||
format,
|
format,
|
||||||
@@ -455,6 +523,7 @@ public class SabrManifestParser {
|
|||||||
representationInfo.segmentBase);
|
representationInfo.segmentBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Format buildFormat(
|
protected Format buildFormat(
|
||||||
String id,
|
String id,
|
||||||
String containerMimeType,
|
String containerMimeType,
|
||||||
@@ -468,62 +537,44 @@ public class SabrManifestParser {
|
|||||||
@C.RoleFlags int roleFlags,
|
@C.RoleFlags int roleFlags,
|
||||||
@C.SelectionFlags int selectionFlags,
|
@C.SelectionFlags int selectionFlags,
|
||||||
String codecs) {
|
String codecs) {
|
||||||
|
|
||||||
String sampleMimeType = getSampleMimeType(containerMimeType, codecs);
|
String sampleMimeType = getSampleMimeType(containerMimeType, codecs);
|
||||||
|
|
||||||
|
// Base builder: fields common to all track types
|
||||||
|
Format.Builder builder = new Format.Builder()
|
||||||
|
.setId(id)
|
||||||
|
.setContainerMimeType(containerMimeType)
|
||||||
|
.setSampleMimeType(sampleMimeType)
|
||||||
|
.setCodecs(codecs)
|
||||||
|
.setAverageBitrate(bitrate) // same semantics as old "bitrate" arg
|
||||||
|
.setSelectionFlags(selectionFlags)
|
||||||
|
.setRoleFlags(roleFlags)
|
||||||
|
.setLanguage(language);
|
||||||
|
|
||||||
if (sampleMimeType != null) {
|
if (sampleMimeType != null) {
|
||||||
if (MimeTypes.isVideo(sampleMimeType)) {
|
if (MimeTypes.isVideo(sampleMimeType)) {
|
||||||
return Format.createVideoContainerFormat(
|
// Replacement for createVideoContainerFormat(...)
|
||||||
id,
|
builder
|
||||||
/* label= */ null,
|
.setWidth(width)
|
||||||
containerMimeType,
|
.setHeight(height)
|
||||||
sampleMimeType,
|
.setFrameRate(frameRate);
|
||||||
codecs,
|
|
||||||
/* metadata= */ null,
|
|
||||||
bitrate,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
frameRate,
|
|
||||||
/* initializationData= */ null,
|
|
||||||
selectionFlags,
|
|
||||||
roleFlags);
|
|
||||||
} else if (MimeTypes.isAudio(sampleMimeType)) {
|
} else if (MimeTypes.isAudio(sampleMimeType)) {
|
||||||
return Format.createAudioContainerFormat(
|
// Replacement for createAudioContainerFormat(...)
|
||||||
id,
|
builder
|
||||||
/* label= */ null,
|
.setChannelCount(audioChannels)
|
||||||
containerMimeType,
|
.setSampleRate(audioSamplingRate);
|
||||||
sampleMimeType,
|
|
||||||
codecs,
|
|
||||||
/* metadata= */ null,
|
|
||||||
bitrate,
|
|
||||||
audioChannels,
|
|
||||||
audioSamplingRate,
|
|
||||||
/* initializationData= */ null,
|
|
||||||
selectionFlags,
|
|
||||||
roleFlags,
|
|
||||||
language);
|
|
||||||
} else if (mimeTypeIsRawText(sampleMimeType)) {
|
} else if (mimeTypeIsRawText(sampleMimeType)) {
|
||||||
return Format.createTextContainerFormat(
|
// Replacement for createTextContainerFormat(...)
|
||||||
id,
|
// You passed Format.NO_VALUE for accessibilityChannel before,
|
||||||
/* label= */ null,
|
// which is already the default, but we can be explicit:
|
||||||
containerMimeType,
|
builder.setAccessibilityChannel(Format.NO_VALUE);
|
||||||
sampleMimeType,
|
|
||||||
codecs,
|
|
||||||
bitrate,
|
|
||||||
selectionFlags,
|
|
||||||
roleFlags,
|
|
||||||
language,
|
|
||||||
Format.NO_VALUE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Format.createContainerFormat(
|
|
||||||
id,
|
// Replacement for createContainerFormat(...) when no specialized type matched
|
||||||
/* label= */ null,
|
return builder.build();
|
||||||
containerMimeType,
|
|
||||||
sampleMimeType,
|
|
||||||
codecs,
|
|
||||||
bitrate,
|
|
||||||
selectionFlags,
|
|
||||||
roleFlags,
|
|
||||||
language);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user