diff --git a/src/HTMLVideo/HTMLVideo.js b/src/HTMLVideo/HTMLVideo.js
index a90b068..902b93e 100644
--- a/src/HTMLVideo/HTMLVideo.js
+++ b/src/HTMLVideo/HTMLVideo.js
@@ -99,6 +99,13 @@ function HTMLVideo(options) {
};
containerElement.appendChild(videoElement);
+ function onFullscreenChanged() {
+ onPropChanged('fullscreen');
+ }
+ videoElement.addEventListener('webkitbeginfullscreen', onFullscreenChanged);
+ videoElement.addEventListener('webkitendfullscreen', onFullscreenChanged);
+ videoElement.addEventListener('fullscreenchange', onFullscreenChanged);
+
var hls = null;
var events = new EventEmitter();
var destroyed = false;
@@ -125,7 +132,8 @@ function HTMLVideo(options) {
volume: false,
muted: false,
playbackSpeed: false,
- videoScale: false
+ videoScale: false,
+ fullscreen: false
};
function getProp(propName) {
@@ -323,6 +331,12 @@ function HTMLVideo(options) {
case 'videoScale': {
return videoElement.style.objectFit || 'contain';
}
+ case 'fullscreen': {
+ if (stream === null) {
+ return null;
+ }
+ return videoElement.webkitDisplayingFullscreen === true || document.fullscreenElement === videoElement;
+ }
default: {
return null;
}
@@ -550,6 +564,23 @@ function HTMLVideo(options) {
break;
}
+ case 'fullscreen': {
+ if (stream === null) break;
+ if (propValue) {
+ if (typeof videoElement.webkitEnterFullscreen === 'function') {
+ videoElement.webkitEnterFullscreen();
+ } else if (typeof videoElement.requestFullscreen === 'function') {
+ videoElement.requestFullscreen();
+ }
+ } else {
+ if (typeof videoElement.webkitExitFullscreen === 'function' && videoElement.webkitDisplayingFullscreen) {
+ videoElement.webkitExitFullscreen();
+ } else if (document.fullscreenElement === videoElement) {
+ document.exitFullscreen();
+ }
+ }
+ break;
+ }
}
}
function command(commandName, commandArgs) {
@@ -571,6 +602,7 @@ function HTMLVideo(options) {
onPropChanged('selectedSubtitlesTrackId');
onPropChanged('audioTracks');
onPropChanged('selectedAudioTrackId');
+ onPropChanged('fullscreen');
getContentType(stream)
.then(function(contentType) {
if (stream !== commandArgs.stream) {
@@ -636,6 +668,7 @@ function HTMLVideo(options) {
onPropChanged('selectedSubtitlesTrackId');
onPropChanged('audioTracks');
onPropChanged('selectedAudioTrackId');
+ onPropChanged('fullscreen');
break;
}
case 'destroy': {
@@ -668,6 +701,9 @@ function HTMLVideo(options) {
videoElement.onvolumechange = null;
videoElement.onratechange = null;
videoElement.textTracks.onchange = null;
+ videoElement.removeEventListener('webkitbeginfullscreen', onFullscreenChanged);
+ videoElement.removeEventListener('webkitendfullscreen', onFullscreenChanged);
+ videoElement.removeEventListener('fullscreenchange', onFullscreenChanged);
containerElement.removeChild(videoElement);
containerElement.removeChild(styleElement);
break;
@@ -727,7 +763,7 @@ HTMLVideo.canPlayStream = function(stream) {
HTMLVideo.manifest = {
name: 'HTMLVideo',
external: false,
- props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'subtitlesOpacity', 'volume', 'muted', 'playbackSpeed', 'videoScale'],
+ props: ['stream', 'loaded', 'paused', 'time', 'duration', 'buffering', 'buffered', 'audioTracks', 'selectedAudioTrackId', 'subtitlesTracks', 'selectedSubtitlesTrackId', 'subtitlesOffset', 'subtitlesSize', 'subtitlesTextColor', 'subtitlesBackgroundColor', 'subtitlesOutlineColor', 'subtitlesOpacity', 'volume', 'muted', 'playbackSpeed', 'videoScale', 'fullscreen'],
commands: ['load', 'unload', 'destroy'],
events: ['propValue', 'propChanged', 'ended', 'error', 'subtitlesTrackLoaded', 'audioTrackLoaded']
};
diff --git a/src/withHTMLSubtitles/withHTMLSubtitles.js b/src/withHTMLSubtitles/withHTMLSubtitles.js
index df42804..9bc2f3f 100644
--- a/src/withHTMLSubtitles/withHTMLSubtitles.js
+++ b/src/withHTMLSubtitles/withHTMLSubtitles.js
@@ -40,6 +40,63 @@ function withHTMLSubtitles(Video) {
containerElement.style.zIndex = '0';
containerElement.appendChild(subtitlesElement);
+ var videoElement = containerElement.querySelector('video');
+ var nativeTextTrack = null;
+ var syntheticNativeTextTracks = [];
+
+ function createNativeTrack() {
+ removeNativeTrack();
+ if (cuesByTime === null || selectedTrackId === null) return false;
+ var selectedTrack = tracks.find(function(track) { return track.id === selectedTrackId; });
+ if (!selectedTrack) return false;
+ var delayMs = delay || 0;
+ nativeTextTrack = videoElement.addTextTrack('subtitles', selectedTrack.label || selectedTrack.lang, selectedTrack.lang || '');
+ syntheticNativeTextTracks.push(nativeTextTrack);
+ cuesByTime.times.forEach(function(time) {
+ cuesByTime[time].forEach(function(cue) {
+ if (cue.startTime !== time) return;
+ var start = (cue.startTime + delayMs) / 1000;
+ var end = (cue.endTime + delayMs) / 1000;
+ if (start < 0) start = 0;
+ if (end <= start) return;
+ nativeTextTrack.addCue(new VTTCue(start, end, cue.text));
+ });
+ });
+ nativeTextTrack.mode = 'showing';
+ return true;
+ }
+ function removeNativeTrack() {
+ if (nativeTextTrack !== null) {
+ nativeTextTrack.mode = 'disabled';
+ nativeTextTrack = null;
+ }
+ }
+ function isNativeTextTrack(track) {
+ return syntheticNativeTextTracks.includes(track);
+ }
+ function getEmbeddedTrackIndex(trackId) {
+ if (typeof trackId !== 'string' || !trackId.startsWith('EMBEDDED_')) {
+ return null;
+ }
+ var index = parseInt(trackId.replace('EMBEDDED_', ''), 10);
+ return isNaN(index) ? null : index;
+ }
+ function isWebkitDisplayingFullscreen() {
+ return videoElement && videoElement.webkitDisplayingFullscreen === true;
+ }
+ function onWebkitBeginFullscreen() {
+ createNativeTrack();
+ subtitlesElement.style.display = 'none';
+ }
+ function onWebkitEndFullscreen() {
+ removeNativeTrack();
+ subtitlesElement.style.display = '';
+ }
+ if (videoElement) {
+ videoElement.addEventListener('webkitbeginfullscreen', onWebkitBeginFullscreen);
+ videoElement.addEventListener('webkitendfullscreen', onWebkitEndFullscreen);
+ }
+
var videoState = {
time: null,
paused: false,
@@ -190,7 +247,7 @@ function withHTMLSubtitles(Video) {
events.emit(eventName, propName, getProp(propName, propValue));
- if (propName === 'selectedSubtitlesTrackId' && propValue !== null && selectedTrackId !== null) {
+ if (propName === 'selectedSubtitlesTrackId' && propValue !== null && selectedTrackId !== null && nativeTextTrack === null) {
setProp('selectedExtraSubtitlesTrackId', null);
}
}
@@ -276,6 +333,24 @@ function withHTMLSubtitles(Video) {
return opacity;
}
+ case 'subtitlesTracks': {
+ if (Array.isArray(videoPropValue) && videoElement && videoElement.textTracks) {
+ return videoPropValue.filter(function(track) {
+ var index = getEmbeddedTrackIndex(track.id);
+ return index === null || !isNativeTextTrack(videoElement.textTracks[index]);
+ });
+ }
+
+ return videoPropValue;
+ }
+ case 'selectedSubtitlesTrackId': {
+ if (typeof videoPropValue === 'string' && videoElement && videoElement.textTracks) {
+ var index = getEmbeddedTrackIndex(videoPropValue);
+ return index !== null && isNativeTextTrack(videoElement.textTracks[index]) ? null : videoPropValue;
+ }
+
+ return videoPropValue;
+ }
default: {
return videoPropValue;
}
@@ -369,6 +444,9 @@ function withHTMLSubtitles(Video) {
cuesByTime = result;
startRenderLoop();
+ if (isWebkitDisplayingFullscreen() && nativeTextTrack === null) {
+ createNativeTrack();
+ }
events.emit('extraSubtitlesTrackLoaded', selectedTrack);
})
.catch(function(error) {
@@ -556,6 +634,7 @@ function withHTMLSubtitles(Video) {
return false;
}
case 'unload': {
+ removeNativeTrack();
stopRenderLoop();
lastTimeIndex = null;
cuesByTime = null;
@@ -571,6 +650,10 @@ function withHTMLSubtitles(Video) {
case 'destroy': {
command('unload');
destroyed = true;
+ if (videoElement) {
+ videoElement.removeEventListener('webkitbeginfullscreen', onWebkitBeginFullscreen);
+ videoElement.removeEventListener('webkitendfullscreen', onWebkitEndFullscreen);
+ }
onPropChanged('extraSubtitlesSize');
onPropChanged('extraSubtitlesOffset');
onPropChanged('extraSubtitlesTextColor');