TVSDK proporciona API y código de muestra para la gestión de periodos de interrupción.
Para implementar la gestión de bloqueos, incluido el suministro de contenido alternativo durante la interrupción:
Configure la aplicación para detectar etiquetas de bloqueo en un manifiesto de flujo en directo.
public void createMediaPlayer {
...
String[] blackoutTags = {BLACKOUTTAG};
PSDKConfig.setSubscribedTags(blackoutTags);
// For example: PTSDKConfig.setSubscribedTags({"#EXT-OATCLS-SCTE35"});
}
Cree oyentes de eventos para eventos de metadatos temporizados 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 temporizados para flujos 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() {
...
}
};
Gestione los objetos TimedMetadata
cuando se ejecute el tiempo MediaPlayer
.
_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 contenido al principio y al final del periodo 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 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 provoca objetos timedMetadata
duplicados para la misma etiqueta suscrita. Para evitar cálculos incorrectos que no se pueden buscar, es muy recomendable comprobar si hay intervalos que no se pueden buscar 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);
}