TVSDK proporciona API y código de ejemplo para gestionar períodos de interrupción.
Para implementar la gestión de interrupciones, incluido el suministro de contenido alternativo durante la interrupción:
Configure la aplicación para detectar etiquetas de interrupción en un manifiesto de flujo en directo.
public void createMediaPlayer {
...
String[] blackoutTags = {BLACKOUTTAG};
PSDKConfig.setSubscribedTags(blackoutTags);
// For example: PTSDKConfig.setSubscribedTags({"#EXT-OATCLS-SCTE35"});
}
Cree detectores de eventos para eventos de metadatos cronometrados en flujos en primer y segundo plano.
private MediaPlayer createMediaPlayer() {
mediaPlayer.addEventListener(MediaPlayer.Event.PLAYBACK, _playbackEventListener);
mediaPlayer.addEventListener(MediaPlayer.Event.BLACKOUTS, _blackoutsEventListener);
}
Implemente controladores de eventos de metadatos cronometrados para las secuencias en primer y segundo plano.
Primer plano:
private final MediaPlayer.PlaybackEventListener _playbackEventListener =
new MediaPlayer.PlaybackEventListener() {
...
@override
public void onTimedMetadata(TimedMetadata timedMetadata) {
if (timedMetadata.getName().equal(BLACKOUTTAG) &&
!_timedMetadataList.contains(timedMetadata)) {
_timedMetadataList.add(timedMetadata);
}
}
...
}
private final MediaPlayer.BlackoutsEventListener _blackoutsEventListener =
new MediaPlayer.BlackoutsEventListener() {
@Override
public void onTimedMetadataInBackgroundItem(TimedMetadata timedMetadata) {
TimedMetadata.Type type = timedMetadata.getType();
if (type.equals(TimedMetadata.Type.TAG) && _mediaPlayer.getPlaybackRange() != null
&& _mediaPlayer.getPlaybackRange().getDuration() > 0) {
if (!_timedMetadataList.contains(timedMetadata) && isBlackoutMetadata(timedMetadata)) {
_timedMetadataList.add(timedMetadata);
}
}
}
@Override
public void onBackgroundManifestFailed() {
...
}
};
Handle TimedMetadata
objetos cuando MediaPlayer
el tiempo corre.
_playbackClockEventListener = new Clock.ClockEventListener() {
@Override
public void onTick(String name) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
/* handle timedmetadata object list */
if (_mediaPlayer != null && _timedMetadataList != null
&& _timedMetadataList.size() > 0) {
if (_lastKnownStatus == MediaPlayer.PlayerState.PLAYING) {
long localTime = _mediaPlayer.getLocalTime();
handleTimedMetadataList(localTime);
}
}
}
});
}
};
Cree métodos para cambiar el contenido al principio y al final del período de interrupción.
private void handleTimedMetadataList(long currentTime) {
for (int i = 0; i < _timedMetadataList.size(); i++) {
TimedMetadata timedMetadata = _timedMetadataList.get(i);
long diff = localTime - timedMetadata.getTime();
if (!_timedMetadataDispatchedList.contains(timedMetadata)
&& diff >= 0
&& diff <= PLAYBACK_CLOCK_INTERVAL
&& _mediaPlayer.shouldTriggerSubscribedTagEvent()) {
// switch to blackout content
if (!_inBlackout && isBlackoutStartTimedMetadata(timedMetadata)) {
MediaResource blackoutMediaResource = createBlackoutMediaResource(timedMetadata);
//1. register current item as background item
_mediaPlayer.registerCurrentItemAsBackgroundItem();
//2. replace current item with blackout item
_mediaPlayer.replaceCurrentItem(blackoutMediaResource);
//3. update qos metrics
_mediaQosProvider.updateMetrics(_mediaPlayer);
//4. maintain state
_inBlackout = true;
resetTimedMetada();
break;
}
// switch back to main content
else if (_inBlackout && isBlackoutEndTimedMetadata(timedMetadata)) {
//1. register current item as background item
_mediaPlayer.unregisterCurrentBackgroundItem();
//2. replace current item with blackout item
_mediaPlayer.replaceCurrentItem(_oldMediaResource);
//3. update qos metrics
_mediaQosProvider.updateMetrics(_mediaPlayer);
//4. maintain state
_inBlackout = false;
resetTimedMetada();
break;
}
}
}
}
Actualice los rangos no buscables si el rango de interrupción está en DVR en el flujo de reproducción.
// prepare and update blackout nonSeekable ranges
List<TimeRange> blackoutRanges = prepareBlackoutRanges(_timedMetadataList);
if (blackoutRanges != null && blackoutRanges.size() > 0) {
int size = blackoutRanges.size();
TimeRange[] blackoutRangesArray = blackoutRanges.toArray(new TimeRange[size]);
BlackoutMetadata blackoutMetadata = new BlackoutMetadata(blackoutRangesArray);
updateBlackoutMetadata(blackoutMetadata);
}
// function to update blackout metadata
private void updateBlackoutMetadata(BlackoutMetadata blackoutMetadata) {
MediaPlayerItem currentItem = _mediaPlayer.getCurrentItem();
if (currentItem != null) {
Metadata metadata = currentItem.getResource().getMetadata();
if (metadata != null) {
MetadataNode metadataNode = ((MetadataNode) metadata);
metadataNode.setNode(DefaultMetadataKeys.BLACKOUT_METADATA_KEY.getValue(),
blackoutMetadata);
for (int i = 0; i < blackoutMetadata.getNonSeekableRanges().length; i++) {
TimeRange timeRange = blackoutMetadata.getNonSeekableRanges()[i];
}
}
}
}
Actualmente, para varios flujos en directo de velocidad de bits, ocasionalmente los perfiles de velocidad de bits ajustable (ABR) pueden no estar sincronizados. Esto causa un duplicado timedMetadata
objetos para la misma etiqueta suscrita. Para evitar cálculos incorrectos que no se puedan buscar, se recomienda encarecidamente comprobar si hay intervalos no buscables superpuestos después de los cálculos, como en el siguiente ejemplo:
List<TimeRange> rangesToRemove = new ArrayList<TimeRange>();
for (int i = 0; i < nonSeekableRanges.size() - 1; i++) {
TimeRange range1 = nonSeekableRanges.get(i);
TimeRange range2 = nonSeekableRanges.get(i + 1);
if (range1.contains(range2.getBegin()) && !rangesToRemove.contains(range2)) {
rangesToRemove.add(range2);
} else if (range2.contains(range1.getBegin()) && !rangesToRemove.contains(range1)) {
rangesToRemove.add(range1);
}
}
if (nonSeekableRanges.size() > 0 && rangesToRemove.size() > 0) {
nonSeekableRanges.removeAll(rangesToRemove);
for (int i = 0; i < rangesToRemove.size(); i++) {
TimeRange range = rangesToRemove.get(i);
}
}
if (nonSeekableRanges.size() > 0 && rangesToRemove.size() > 0) {
nonSeekableRanges.removeAll(rangesToRemove);
}