Skip to content

Commit

Permalink
Merge pull request #1 from TradeCast/feat/tracks-session-restoring
Browse files Browse the repository at this point in the history
feat(chromecast): added tracks support, added session restoring support
  • Loading branch information
jbreemhaar committed Mar 12, 2021
2 parents d6b0276 + fe36d04 commit c9d7b15
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 40 deletions.
16 changes: 11 additions & 5 deletions docs/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
<head>
<meta charset=utf-8 />
<title>silvermine-videojs-chromecast Demo</title>
<link href="https://unpkg.com/video.js@6.1.0/dist/video-js.css" rel="stylesheet">
<script src="https://unpkg.com/video.js@6.1.0/dist/video.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.6/video-js.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/video.js/7.11.6/video.js" ></script>
<script src="../../dist/silvermine-videojs-chromecast.min.js"></script>
<link href="../../dist/silvermine-videojs-chromecast.css" rel="stylesheet">
<script type="text/javascript" src="https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
<script type="text/javascript" src="//www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1"></script>
</head>
<body>
<h1>Demo of <code>silvermine-videojs-chromecast</code></h1>

<video id="video_1" class="video-js vjs-default-skin" controls preload="auto" data-setup='{ "fluid": "true" }'>
<source src="http://www.caminandes.com/download/03_caminandes_llamigos_1080p.mp4" type="video/mp4">
<video crossorigin="anonymous" id="video_1" class="video-js vjs-default-skin" controls preload="auto" data-setup='{ "fluid": "true" }'>
<source src="https://storage.googleapis.com/webfundamentals-assets/videos/chrome.webm" type="video/webm" />
<source src="https://storage.googleapis.com/webfundamentals-assets/videos/chrome.mp4" type="video/mp4" />
<track src="https://track-demonstration.glitch.me/chrome-subtitles-en.vtt" label="English captions"
kind="captions" srclang="en" default>
<track src="https://track-demonstration.glitch.me/chrome-subtitles-zh.vtt" label="中文字幕"
kind="captions" srclang="zh">
</video>

<script>
Expand All @@ -28,6 +33,7 @@ <h1>Demo of <code>silvermine-videojs-chromecast</code></h1>

player.chromecast();
});

</script>

</body>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@silvermine/videojs-chromecast",
"version": "1.2.1",
"version": "1.2.2",
"description": "video.js plugin for casting to chromecast",
"main": "src/js/index.js",
"scripts": {
Expand Down
48 changes: 39 additions & 9 deletions src/js/chromecast/ChromecastSessionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ ChromecastSessionManager = Class.extend(/** @lends ChromecastSessionManager.prot
this.player.on('dispose', this._removeCastContextEventListeners.bind(this));

this._notifyPlayerOfDevicesAvailabilityChange(this.getCastContext().getCastState());

this.remotePlayer = new cast.framework.RemotePlayer();
this.remotePlayerController = new cast.framework.RemotePlayerController(this.remotePlayer);
},
Expand Down Expand Up @@ -89,6 +88,37 @@ ChromecastSessionManager = Class.extend(/** @lends ChromecastSessionManager.prot
if (event.sessionState === cast.framework.SessionState.SESSION_ENDED) {
this.player.trigger('chromecastDisconnected');
this._reloadTech();
} else if (event.sessionState === cast.framework.SessionState.SESSION_RESUMED) {
this.onSessionResumed();
}
},

/**
* generate session source
* @private
*/
reloadTechWithSources: function(mediaStatus) {
var currentTime = mediaStatus.currentTime,
isPlaying = mediaStatus.playerState === 'PLAYING',
sources = [ { id: mediaStatus.media.contentId, src: mediaStatus.media.contentUrl, type: mediaStatus.media.contentType } ];

this._reloadTech(currentTime, isPlaying, sources);
},

/**
* on session resumed
*/
onSessionResumed: function() {
var instance = cast.framework.CastContext.getInstance(),
castSession = instance.getCurrentSession(),
mediaStatus = castSession.getMediaSession();

hasConnected = true;

if (mediaStatus) {
this.reloadTechWithSources(mediaStatus);
} else {
this._reloadTech();
}
},

Expand Down Expand Up @@ -168,24 +198,24 @@ ChromecastSessionManager = Class.extend(/** @lends ChromecastSessionManager.prot
*
* @private
*/
_reloadTech: function() {
_reloadTech: function(sessionCurrentTime, sessionPlaying, sessionSources) {
var player = this.player,
currentTime = player.currentTime(),
wasPaused = player.paused(),
sources = player.currentSources();
currentTime = sessionCurrentTime || player.currentTime(),
wasPlaying = sessionPlaying || !player.paused(),
sources = sessionSources || player.currentSources();

// eslint-disable-next-line no-console
// Reload the current source(s) to re-lookup and use the currently available Tech.
// The chromecast Tech gets used if `ChromecastSessionManager.isChromecastConnected`
// is true (effectively, if a chromecast session is currently in progress),
// otherwise Video.js continues to search through the Tech list for other eligible
// Tech to use, such as the HTML5 player.
player.src(sources);

player.ready(function() {
if (wasPaused) {
player.pause();
} else {
if (wasPlaying) {
player.play();
} else {
player.pause();
}
player.currentTime(currentTime || 0);
});
Expand Down
5 changes: 3 additions & 2 deletions src/js/enableChromecast.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ function configureCastContext(options) {
// must end any existing session before trying to cast from this player instance.
autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED,
});

}

/**
Expand All @@ -42,8 +43,8 @@ function onChromecastRequested(player) {
}

/**
* Adds the Chromecast button to the player's control bar, if one does not already exist,
* then starts listening for the `chromecastRequested` event.
* Adds the Chromecast button to the player's control bar, if one does not already
* exist, then starts listening for the `chromecastRequested` event
*
* @private
* @param player {object} a Video.js player instance
Expand Down
161 changes: 138 additions & 23 deletions src/js/tech/ChromecastTech.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ ChromecastTech = {
* @see {@link https://developers.google.com/cast/|Google Cast}
*/
constructor: function(options) {
var subclass;
var mediaSession,
textTrackDisplay,
subclass;

this._eventListeners = [];

this.options = options;
this.videojsPlayer = this.videojs(options.playerId);
this._chromecastSessionManager = this.videojsPlayer.chromecastSessionManager;

Expand All @@ -49,7 +51,7 @@ ChromecastTech = {
this._remotePlayer = this._chromecastSessionManager.getRemotePlayer();
this._remotePlayerController = this._chromecastSessionManager.getRemotePlayerController();
this._listenToPlayerControllerEvents();
this.on('dispose', this._removeAllEventListeners.bind(this));
this.on('dispose', this._onDispose.bind(this));

this._hasPlayedAnyItem = false;
this._requestTitle = options.requestTitleFn || _.noop;
Expand All @@ -58,10 +60,22 @@ ChromecastTech = {
// See `currentTime` function
this._initialStartTime = options.startTime || 0;

this._playSource(options.source, this._initialStartTime);
mediaSession = this._getMediaSession();
if (mediaSession && mediaSession.media && mediaSession.media.contentId === options.source.id) {
this.onLoadSessionSucces();
} else {
this._playSource(options.source, this._initialStartTime);
}

this.ready(function() {
this.setMuted(options.muted);
}.bind(this));
this.videojsPlayer.remoteTextTracks().on('change', this._onChangeTrack.bind(this));
textTrackDisplay = this.videojsPlayer.getChild('TextTrackDisplay');

if (textTrackDisplay) {
textTrackDisplay.hide();
}

return subclass;
},
Expand Down Expand Up @@ -144,6 +158,53 @@ ChromecastTech = {
this._playSource(source, 0);
},

/**
* transform trackdata in cast Track
* @param trackData
*/
generateTrack: function(trackData, id) {
var sub = new chrome.cast.media.Track(id, chrome.cast.media.TrackType.TEXT),
textTrackTypes;

textTrackTypes = {
subtitles: chrome.cast.media.TextTrackType.SUBTITLES,
captions: chrome.cast.media.TextTrackType.CAPTIONS,
descriptions: chrome.cast.media.TextTrackType.DESCRIPTIONS,
chapters: chrome.cast.media.TextTrackType.CHAPTERS,
metadata: chrome.cast.media.TextTrackType.METADATA,
};

sub.trackContentId = trackData.src;
sub.subtype = textTrackTypes[trackData.kind];
sub.name = trackData.language;
sub.language = trackData.language;
return sub;
},

/**
* _onChangeTrack
* @private
*/
_onChangeTrack: function() {
var castSession = cast.framework.CastContext.getInstance().getCurrentSession(),
media = castSession.getMediaSession(),
index, subtitles, tracksInfoRequest, i;


if (castSession) {
index = [];
subtitles = this.videojsPlayer.remoteTextTracks();
for (i = 0; i < subtitles.length; i++) {
if (subtitles[i].mode === 'showing') {
index = [ i ];
}
}
tracksInfoRequest = new chrome.cast.media.EditTracksInfoRequest(index);

media.editTracksInfo(tracksInfoRequest, _.noop, _.noop);
}
},

/**
* Plays the given source, beginning at an optional starting time.
*
Expand All @@ -154,11 +215,17 @@ ChromecastTech = {
*/
_playSource: function(source, startTime) {
var castSession = this._getCastSession(),
mediaInfo = new chrome.cast.media.MediaInfo(source.src, source.type),
mediaInfo = new chrome.cast.media.MediaInfo(),
title = this._requestTitle(source),
subtitle = this._requestSubtitle(source),
customData = this._requestCustomData(source),
request;
textTrackJsonTracks = this.videojsPlayer.textTracksJson_,
request,
i;

mediaInfo.contentId = source.id;
mediaInfo.contentUrl = source.src;
mediaInfo.contentType = source.type;

this.trigger('waiting');
this._clearSessionTimeout();
Expand All @@ -167,6 +234,16 @@ ChromecastTech = {
mediaInfo.metadata.metadataType = chrome.cast.media.MetadataType.GENERIC;
mediaInfo.metadata.title = title;
mediaInfo.metadata.subtitle = subtitle;
mediaInfo.tracks = [];
mediaInfo.activeTrackIds = [];

for (i = 0; i < textTrackJsonTracks.length; i++) {
mediaInfo.tracks.push(this.generateTrack(textTrackJsonTracks[i], i));
if (textTrackJsonTracks[i].mode === 'showing') {
mediaInfo.activeTrackIds.push(i);
}
}

if (customData) {
mediaInfo.customData = customData;
}
Expand All @@ -181,20 +258,45 @@ ChromecastTech = {
this._isMediaLoading = true;
this._hasPlayedCurrentItem = false;
castSession.loadMedia(request)
.then(function() {
if (!this._hasPlayedAnyItem) {
// `triggerReady` is required here to notify the Video.js player that the
// Tech has been initialized and is ready.
this.triggerReady();
}
this.trigger('loadstart');
this.trigger('loadeddata');
this.trigger('play');
this.trigger('playing');
this._hasPlayedAnyItem = true;
this._isMediaLoading = false;
this._getMediaSession().addUpdateListener(this._onMediaSessionStatusChanged.bind(this));
}.bind(this), this._triggerErrorEvent.bind(this));
.then(this.onLoadSessionSucces.bind(this), this._triggerErrorEvent.bind(this));
},

/**
* onLoadSessionSucces
*/
onLoadSessionSucces: function() {
if (!this._hasPlayedAnyItem) {
// `triggerReady` is required here to notify the Video.js player that the
// Tech has been initialized and is ready.
this.triggerReady();
}

this.trigger('loadstart');
this.trigger('loadeddata');
this.trigger('play');
this.trigger('playing');
this.videojsPlayer.hasStarted(true);
this._hasPlayedAnyItem = true;
this._isMediaLoading = false;
clearTimeout(this.playStateValidationTimeout);
this.playStateValidationTimeout = window.setTimeout(this.validatePlayState.bind(this), 1000);
this._getMediaSession().addUpdateListener(this._onMediaSessionStatusChanged.bind(this));
},

/**
* play state validation
* making sure everything between cast and local player are in sync
*/
validatePlayState: function() {
var textTrackDisplay = this.videojsPlayer.getChild('TextTrackDisplay');

this._triggerTimeUpdateEvent();
this._onPlayerStateChanged();
this._onChangeTrack();

if (textTrackDisplay) {
textTrackDisplay.hide();
}
},

/**
Expand Down Expand Up @@ -532,6 +634,20 @@ ChromecastTech = {
this._eventListeners.push(listener);
},

/**
* on dispose
* @private
*/
_onDispose: function() {
var textTrackDisplay = this.videojsPlayer.getChild('TextTrackDisplay');

if (textTrackDisplay) {
textTrackDisplay.show();
}
clearTimeout(this.playStateValidationTimeout);
this._removeAllEventListeners();
},

/**
* Removes all event listeners that were registered with global objects during the
* lifetime of this Tech. See {@link _addEventListener} for more information about why
Expand Down Expand Up @@ -742,10 +858,9 @@ module.exports = function(videojs) {
ChromecastTechImpl.prototype.featuresFullscreenResize = true;
ChromecastTechImpl.prototype.featuresTimeupdateEvents = true;
ChromecastTechImpl.prototype.featuresProgressEvents = false;
// Text tracks are not supported in this version
ChromecastTechImpl.prototype.featuresNativeTextTracks = false;
ChromecastTechImpl.prototype.featuresNativeAudioTracks = false;
ChromecastTechImpl.prototype.featuresNativeVideoTracks = false;
ChromecastTechImpl.prototype.featuresNativeAudioTracks = true;
ChromecastTechImpl.prototype.featuresNativeVideoTracks = true;

// Give ChromecastTech class instances a reference to videojs
ChromecastTechImpl.prototype.videojs = videojs;
Expand Down

0 comments on commit c9d7b15

Please sign in to comment.