fix: refactor side loaded text tracks management (#4158)
* fix: refactor side loaded text tracks management More textTracks in source. android/ios: ensure text tracks are not selected by default android/ios make textTrack field not nullable clean up doc check compatibility with the old api Add comments on deprecated JS apis Apply API change on basic sample * chore: fix linter * fix(ios): fix build with caching & remove warnings
This commit is contained in:
@@ -11,12 +11,18 @@ import com.facebook.react.bridge.ReadableMap
|
||||
class SideLoadedTextTrackList {
|
||||
var tracks = ArrayList<SideLoadedTextTrack>()
|
||||
|
||||
/** return true if this and src are equals */
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null || other !is SideLoadedTextTrackList) return false
|
||||
return tracks == other.tracks
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun parse(src: ReadableArray?): SideLoadedTextTrackList? {
|
||||
if (src == null) {
|
||||
return null
|
||||
}
|
||||
var sideLoadedTextTrackList = SideLoadedTextTrackList()
|
||||
val sideLoadedTextTrackList = SideLoadedTextTrackList()
|
||||
for (i in 0 until src.size()) {
|
||||
val textTrack: ReadableMap = src.getMap(i)
|
||||
sideLoadedTextTrackList.tracks.add(SideLoadedTextTrack.parse(textTrack))
|
||||
|
@@ -62,6 +62,11 @@ class Source {
|
||||
*/
|
||||
var cmcdProps: CMCDProps? = null
|
||||
|
||||
/**
|
||||
* The list of sideLoaded text tracks
|
||||
*/
|
||||
var sideLoadedTextTracks: SideLoadedTextTrackList? = null
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(uriString, uri, startPositionMs, cropStartMs, cropEndMs, extension, metadata, headers)
|
||||
|
||||
/** return true if this and src are equals */
|
||||
@@ -74,7 +79,8 @@ class Source {
|
||||
startPositionMs == other.startPositionMs &&
|
||||
extension == other.extension &&
|
||||
drmProps == other.drmProps &&
|
||||
cmcdProps == other.cmcdProps
|
||||
cmcdProps == other.cmcdProps &&
|
||||
sideLoadedTextTracks == other.sideLoadedTextTracks
|
||||
)
|
||||
}
|
||||
|
||||
@@ -139,6 +145,7 @@ class Source {
|
||||
private const val PROP_SRC_DRM = "drm"
|
||||
private const val PROP_SRC_CMCD = "cmcd"
|
||||
private const val PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION = "textTracksAllowChunklessPreparation"
|
||||
private const val PROP_SRC_TEXT_TRACKS = "textTracks"
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
private fun getUriFromAssetId(context: Context, uriString: String): Uri? {
|
||||
@@ -198,6 +205,7 @@ class Source {
|
||||
source.drmProps = parse(safeGetMap(src, PROP_SRC_DRM))
|
||||
source.cmcdProps = CMCDProps.parse(safeGetMap(src, PROP_SRC_CMCD))
|
||||
source.textTracksAllowChunklessPreparation = safeGetBool(src, PROP_SRC_TEXT_TRACKS_ALLOW_CHUNKLESS_PREPARATION, true)
|
||||
source.sideLoadedTextTracks = SideLoadedTextTrackList.parse(safeGetArray(src, PROP_SRC_TEXT_TRACKS))
|
||||
|
||||
val propSrcHeadersArray = safeGetArray(src, PROP_SRC_HEADERS)
|
||||
if (propSrcHeadersArray != null) {
|
||||
|
@@ -3,7 +3,6 @@ package com.brentvatne.common.toolbox
|
||||
import com.facebook.react.bridge.Dynamic
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import java.util.HashMap
|
||||
|
||||
/*
|
||||
* Toolbox to safe parsing of <Video props
|
||||
@@ -54,6 +53,17 @@ object ReactBridgeUtils {
|
||||
|
||||
@JvmStatic fun safeGetFloat(map: ReadableMap?, key: String?): Float = safeGetFloat(map, key, 0.0f)
|
||||
|
||||
@JvmStatic fun safeParseInt(value: String?, default: Int): Int {
|
||||
if (value == null) {
|
||||
return default
|
||||
}
|
||||
return try {
|
||||
value.toInt()
|
||||
} catch (e: java.lang.Exception) {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* toStringMap converts a [ReadableMap] into a HashMap.
|
||||
*
|
||||
|
@@ -108,7 +108,6 @@ import com.brentvatne.common.api.ControlsConfig;
|
||||
import com.brentvatne.common.api.DRMProps;
|
||||
import com.brentvatne.common.api.ResizeMode;
|
||||
import com.brentvatne.common.api.SideLoadedTextTrack;
|
||||
import com.brentvatne.common.api.SideLoadedTextTrackList;
|
||||
import com.brentvatne.common.api.Source;
|
||||
import com.brentvatne.common.api.SubtitleStyle;
|
||||
import com.brentvatne.common.api.TimedMetadata;
|
||||
@@ -116,6 +115,7 @@ import com.brentvatne.common.api.Track;
|
||||
import com.brentvatne.common.api.VideoTrack;
|
||||
import com.brentvatne.common.react.VideoEventEmitter;
|
||||
import com.brentvatne.common.toolbox.DebugLog;
|
||||
import com.brentvatne.common.toolbox.ReactBridgeUtils;
|
||||
import com.brentvatne.react.BuildConfig;
|
||||
import com.brentvatne.react.R;
|
||||
import com.brentvatne.react.ReactNativeVideoManager;
|
||||
@@ -230,9 +230,8 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
private String audioTrackValue;
|
||||
private String videoTrackType;
|
||||
private String videoTrackValue;
|
||||
private String textTrackType;
|
||||
private String textTrackType = "disabled";
|
||||
private String textTrackValue;
|
||||
private SideLoadedTextTrackList textTracks;
|
||||
private boolean disableFocus;
|
||||
private boolean focusable = true;
|
||||
private BufferingStrategy.BufferingStrategyEnum bufferingStrategy;
|
||||
@@ -1126,11 +1125,11 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
|
||||
private ArrayList<MediaSource> buildTextSources() {
|
||||
ArrayList<MediaSource> textSources = new ArrayList<>();
|
||||
if (textTracks == null) {
|
||||
if (source.getSideLoadedTextTracks() == null) {
|
||||
return textSources;
|
||||
}
|
||||
|
||||
for (SideLoadedTextTrack track : textTracks.getTracks()) {
|
||||
for (SideLoadedTextTrack track : source.getSideLoadedTextTracks().getTracks()) {
|
||||
MediaSource textSource = buildTextSource(track.getTitle(),
|
||||
track.getUri(),
|
||||
track.getType(),
|
||||
@@ -1844,11 +1843,6 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
adLanguage = language;
|
||||
}
|
||||
|
||||
public void setTextTracks(SideLoadedTextTrackList textTracks) {
|
||||
this.textTracks = textTracks;
|
||||
reloadSource(); // FIXME Shall be moved inside source
|
||||
}
|
||||
|
||||
private void reloadSource() {
|
||||
playerNeedsSource = true;
|
||||
initializePlayer();
|
||||
@@ -1928,64 +1922,67 @@ public class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
}
|
||||
} else if ("index".equals(type)) {
|
||||
int iValue = Integer.parseInt(value);
|
||||
|
||||
if (trackType == C.TRACK_TYPE_VIDEO && groups.length == 1) {
|
||||
groupIndex = 0;
|
||||
if (iValue < groups.get(groupIndex).length) {
|
||||
tracks.set(0, iValue);
|
||||
int iValue = ReactBridgeUtils.safeParseInt(value, -1);
|
||||
if (iValue != -1) {
|
||||
if (trackType == C.TRACK_TYPE_VIDEO && groups.length == 1) {
|
||||
groupIndex = 0;
|
||||
if (iValue < groups.get(groupIndex).length) {
|
||||
tracks.set(0, iValue);
|
||||
}
|
||||
} else if (iValue < groups.length) {
|
||||
groupIndex = iValue;
|
||||
}
|
||||
} else if (iValue < groups.length) {
|
||||
groupIndex = iValue;
|
||||
}
|
||||
} else if ("resolution".equals(type)) {
|
||||
int height = Integer.parseInt(value);
|
||||
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
|
||||
TrackGroup group = groups.get(i);
|
||||
Format closestFormat = null;
|
||||
int closestTrackIndex = -1;
|
||||
boolean usingExactMatch = false;
|
||||
for (int j = 0; j < group.length; j++) {
|
||||
Format format = group.getFormat(j);
|
||||
if (format.height == height) {
|
||||
groupIndex = i;
|
||||
tracks.set(0, j);
|
||||
closestFormat = null;
|
||||
closestTrackIndex = -1;
|
||||
usingExactMatch = true;
|
||||
break;
|
||||
} else if (isUsingContentResolution) {
|
||||
// When using content resolution rather than ads, we need to try and find the closest match if there is no exact match
|
||||
if (closestFormat != null) {
|
||||
if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) {
|
||||
// Higher quality match
|
||||
int height = ReactBridgeUtils.safeParseInt(value, -1);
|
||||
if (height != -1) {
|
||||
for (int i = 0; i < groups.length; ++i) { // Search for the exact height
|
||||
TrackGroup group = groups.get(i);
|
||||
Format closestFormat = null;
|
||||
int closestTrackIndex = -1;
|
||||
boolean usingExactMatch = false;
|
||||
for (int j = 0; j < group.length; j++) {
|
||||
Format format = group.getFormat(j);
|
||||
if (format.height == height) {
|
||||
groupIndex = i;
|
||||
tracks.set(0, j);
|
||||
closestFormat = null;
|
||||
closestTrackIndex = -1;
|
||||
usingExactMatch = true;
|
||||
break;
|
||||
} else if (isUsingContentResolution) {
|
||||
// When using content resolution rather than ads, we need to try and find the closest match if there is no exact match
|
||||
if (closestFormat != null) {
|
||||
if ((format.bitrate > closestFormat.bitrate || format.height > closestFormat.height) && format.height < height) {
|
||||
// Higher quality match
|
||||
closestFormat = format;
|
||||
closestTrackIndex = j;
|
||||
}
|
||||
} else if (format.height < height) {
|
||||
closestFormat = format;
|
||||
closestTrackIndex = j;
|
||||
}
|
||||
} else if(format.height < height) {
|
||||
closestFormat = format;
|
||||
closestTrackIndex = j;
|
||||
}
|
||||
}
|
||||
}
|
||||
// This is a fallback if the new period contains only higher resolutions than the user has selected
|
||||
if (closestFormat == null && isUsingContentResolution && !usingExactMatch) {
|
||||
// No close match found - so we pick the lowest quality
|
||||
int minHeight = Integer.MAX_VALUE;
|
||||
for (int j = 0; j < group.length; j++) {
|
||||
Format format = group.getFormat(j);
|
||||
if (format.height < minHeight) {
|
||||
minHeight = format.height;
|
||||
groupIndex = i;
|
||||
tracks.set(0, j);
|
||||
// This is a fallback if the new period contains only higher resolutions than the user has selected
|
||||
if (closestFormat == null && isUsingContentResolution && !usingExactMatch) {
|
||||
// No close match found - so we pick the lowest quality
|
||||
int minHeight = Integer.MAX_VALUE;
|
||||
for (int j = 0; j < group.length; j++) {
|
||||
Format format = group.getFormat(j);
|
||||
if (format.height < minHeight) {
|
||||
minHeight = format.height;
|
||||
groupIndex = i;
|
||||
tracks.set(0, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Selecting the closest match found
|
||||
if (closestFormat != null && closestTrackIndex != -1) {
|
||||
// We found the closest match instead of an exact one
|
||||
groupIndex = i;
|
||||
tracks.set(0, closestTrackIndex);
|
||||
// Selecting the closest match found
|
||||
if (closestFormat != null && closestTrackIndex != -1) {
|
||||
// We found the closest match instead of an exact one
|
||||
groupIndex = i;
|
||||
tracks.set(0, closestTrackIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (trackType == C.TRACK_TYPE_TEXT && Util.SDK_INT > 18) { // Text default
|
||||
|
@@ -8,7 +8,6 @@ import com.brentvatne.common.api.BufferConfig
|
||||
import com.brentvatne.common.api.BufferingStrategy
|
||||
import com.brentvatne.common.api.ControlsConfig
|
||||
import com.brentvatne.common.api.ResizeMode
|
||||
import com.brentvatne.common.api.SideLoadedTextTrackList
|
||||
import com.brentvatne.common.api.Source
|
||||
import com.brentvatne.common.api.SubtitleStyle
|
||||
import com.brentvatne.common.api.ViewType
|
||||
@@ -16,7 +15,6 @@ import com.brentvatne.common.react.EventTypes
|
||||
import com.brentvatne.common.toolbox.DebugLog
|
||||
import com.brentvatne.common.toolbox.ReactBridgeUtils
|
||||
import com.brentvatne.react.ReactNativeVideoManager
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.ViewGroupManager
|
||||
@@ -38,7 +36,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View
|
||||
private const val PROP_SELECTED_TEXT_TRACK = "selectedTextTrack"
|
||||
private const val PROP_SELECTED_TEXT_TRACK_TYPE = "type"
|
||||
private const val PROP_SELECTED_TEXT_TRACK_VALUE = "value"
|
||||
private const val PROP_TEXT_TRACKS = "textTracks"
|
||||
private const val PROP_PAUSED = "paused"
|
||||
private const val PROP_MUTED = "muted"
|
||||
private const val PROP_AUDIO_OUTPUT = "audioOutput"
|
||||
@@ -180,12 +177,6 @@ class ReactExoplayerViewManager(private val config: ReactExoplayerConfig) : View
|
||||
videoView.setSelectedTextTrack(typeString, value)
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_TEXT_TRACKS)
|
||||
fun setTextTracks(videoView: ReactExoplayerView, textTracks: ReadableArray?) {
|
||||
val sideLoadedTextTracks = SideLoadedTextTrackList.parse(textTracks)
|
||||
videoView.setTextTracks(sideLoadedTextTracks)
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_PAUSED, defaultBoolean = false)
|
||||
fun setPaused(videoView: ReactExoplayerView, paused: Boolean) {
|
||||
videoView.setPausedModifier(paused)
|
||||
|
Reference in New Issue
Block a user