Ye sh Ye sh ... это очень просто. На самом деле, это очень просто. На самом деле, проще, чем все, что вы можете себе представить .
// TODO: Start time, is a fake timestamp. Use correct values for your implementation.
let currentTime = new Date();
// Convert from milliseconds to seconds.
currentTime = currentTime / 1000;
let sectionStartAbsoluteTime = currentTime;
// Duration should be -1 for live streams.
mediaInfo.duration = -1;
// TODO: Set on the receiver for your implementation.
mediaInfo.startAbsoluteTime = currentTime;
mediaInfo.metadata.sectionStartAbsoluteTime = sectionStartAbsoluteTime;
// TODO: Set on the receiver for your implementation.
mediaInfo.metadata.sectionStartTimeInMedia = 0;
mediaInfo.metadata.sectionDuration = this.mediaContents[mediaIndex]['duration'];
let item = new chrome.cast.media.QueueItem(mediaInfo);
request.queueData = new chrome.cast.media.QueueData();
request.queueData.items = [item];
request.queueData.name = "Sample Queue for Live";
}
// Do not immediately start playing if the player was previously PAUSED.
if (!this.playerStateBeforeSwitch || this.playerStateBeforeSwitch == PLAYER_STATE.PAUSED) {
request.autoplay = false;
} else {
request.autoplay = true;
}
cast.framework.CastContext.getInstance().getCurrentSession().loadMedia(request).then(
function () {
console.log('Remote media loaded');
}.bind(this),
function (errorCode) {
this.playerState = PLAYER_STATE.IDLE;
console.log('Remote media load error: ' +
CastPlayer.getErrorMessage(errorCode));
this.playerHandler.updateDisplay();
}.bind(this));
}.bind(this);
playerTarget.isMediaLoaded = function (mediaIndex) {
let session = cast.framework.CastContext.getInstance().getCurrentSession();
if (!session) return false;
let media = session.getMediaSession();
if (!media) return false;
if (media.playerState == PLAYER_STATE.IDLE) {
return false;
}
// No need to verify local mediaIndex content.
return true;
}.bind(this);
/**
* @return {number?} Current media time for the content. Always returns
* media time even if in clock time (conversion done when displaying).
*/
playerTarget.getCurrentMediaTime = function () {
if (this.isLiveContent && this.mediaInfo.metadata &&
this.mediaInfo.metadata.sectionStartTimeInMedia) {
return this.remotePlayer.currentTime - this.mediaInfo.metadata.sectionStartTimeInMedia;
} else {
// VOD and live scenerios where live metadata is not provided.
return this.remotePlayer.currentTime;
}
}.bind(this);
/**
* @return {number?} media time duration for the content. Always returns
* media time even if in clock time (conversion done when displaying).
*/
playerTarget.getMediaDuration = function () {
if (this.isLiveContent) {
// Scenerios when live metadata is not provided.
if (this.mediaInfo.metadata == undefined ||
this.mediaInfo.metadata.sectionDuration == undefined ||
this.mediaInfo.metadata.sectionStartTimeInMedia == undefined) {
return null;
}
return this.mediaInfo.metadata.sectionDuration;
} else {
return this.remotePlayer.duration;
}
}.bind(this);
playerTarget.updateDisplay = function () {
let castSession = cast.framework.CastContext.getInstance().getCurrentSession();
if (castSession && castSession.getMediaSession() && castSession.getMediaSession().media) {
let media = castSession.getMediaSession();
let mediaInfo = media.media;
// image placeholder for video view
var vi = document.getElementById('video_image');
if (mediaInfo.metadata && mediaInfo.metadata.images &&
mediaInfo.metadata.images.length > 0) {
vi.src = mediaInfo.metadata.images[0].url;
}
// playerstate view
document.getElementById('playerstate').style.display = 'block';
document.getElementById('playerstatebg').style.display = 'block';
document.getElementById('video_image_overlay').style.display = 'block';
let mediaTitle = '';
let mediaEpisodeTitle = '';
let mediaSubtitle = '';
if (mediaInfo.metadata) {
mediaTitle = mediaInfo.metadata.title;
mediaEpisodeTitle = mediaInfo.metadata.episodeTitle;
// Append episode title if present
mediaTitle = mediaEpisodeTitle ? mediaTitle + ': ' + mediaEpisodeTitle : mediaTitle;
// Do not display mediaTitle if not defined.
mediaTitle = (mediaTitle) ? mediaTitle + ' ' : '';
mediaSubtitle = mediaInfo.metadata.subtitle;
mediaSubtitle = (mediaSubtitle) ? mediaSubtitle + ' ' : '';
}
if (DEMO_MODE) {
document.getElementById('playerstate').innerHTML =
(ENABLE_LIVE ? 'Live Content ' : 'Sample Video ') + media.playerState + ' on Chromecast';
// media_info view
document.getElementById('media_title').innerHTML = (ENABLE_LIVE ? 'Live Content' : 'Sample Video');
document.getElementById('media_subtitle').innerHTML = '';
} else {
document.getElementById('playerstate').innerHTML =
mediaTitle + media.playerState + ' on ' +
castSession.getCastDevice().friendlyName;
// media_info view
document.getElementById('media_title').innerHTML = mediaTitle;
document.getElementById('media_subtitle').innerHTML = mediaSubtitle;
}
// live information
if (mediaInfo.streamType == chrome.cast.media.StreamType.LIVE) {
this.liveSeekableRange = media.liveSeekableRange;
let live_indicator = document.getElementById('live_indicator');
live_indicator.style.display = 'block';
// Display indicator if current time is close to the end of
// the seekable range.
if (this.liveSeekableRange && (Math.abs(media.getEstimatedTime() - this.liveSeekableRange.end) < LIVE_INDICATOR_BUFFER)) {
live_indicator.src = "imagefiles/live_indicator_active.png";
} else {
live_indicator.src = "imagefiles/live_indicator_inactive.png";
}
} else {
document.getElementById('live_indicator').style.display = 'none';
}
} else {
// playerstate view
document.getElementById('playerstate').style.display = 'none';
document.getElementById('playerstatebg').style.display = 'none';
document.getElementById('video_image_overlay').style.display = 'none';
// media_info view
document.getElementById('media_title').innerHTML = "";
document.getElementById('media_subtitle').innerHTML = "";
}
}.bind(this);
playerTarget.updateCurrentTimeDisplay = function () {
this.playerHandler.setTimeString(document.getElementById('currentTime'), this.playerHandler.getCurrentMediaTime());
}.bind(this);
playerTarget.updateDurationDisplay = function () {
this.playerHandler.setTimeString(document.getElementById('duration'), this.playerHandler.getMediaDuration());
}.bind(this);
playerTarget.setTimeString = function (element, time) {
let currentTimeString = this.getMediaTimeString(time);
if (this.isLiveContent) {
if (currentTimeString == null) {
element.style.display = 'none';
return;
}
// clock time
if (this.mediaInfo.metadata && this.mediaInfo.metadata.sectionStartAbsoluteTime !== undefined) {
element.style.display = 'flex';
element.innerHTML = this.getClockTimeString(time + this.mediaInfo.metadata.sectionStartAbsoluteTime);
} else {
// media time
element.style.display = 'flex';
element.innerHTML = currentTimeString;
}
} else {
if (currentTimeString !== null) {
element.style.display = 'flex';
element.innerHTML = currentTimeString;
} else {
element.style.display = 'none';
}
}
}.bind(this);
playerTarget.setVolume = function (volumeSliderPosition) {
var currentVolume = this.remotePlayer.volumeLevel;
var p = document.getElementById('audio_bg_level');
if (volumeSliderPosition < FULL_VOLUME_HEIGHT) {
p.style.height = volumeSliderPosition + 'px';
p.style.marginTop = -volumeSliderPosition + 'px';
currentVolume = volumeSliderPosition / FULL_VOLUME_HEIGHT;
} else {
currentVolume = 1;
}
this.remotePlayer.volumeLevel = currentVolume;
this.remotePlayerController.setVolumeLevel();
}.bind(this);
playerTarget.mute = function () {
if (!this.remotePlayer.isMuted) {
this.remotePlayerController.muteOrUnmute();
}
}.bind(this);
playerTarget.unMute = function () {
if (this.remotePlayer.isMuted) {
this.remotePlayerController.muteOrUnmute();
}
}.bind(this);
playerTarget.isMuted = function () {
return this.remotePlayer.isMuted;
}.bind(this);
playerTarget.seekTo = function (time) {
this.remotePlayer.currentTime = time;
this.remotePlayerController.seek();
}.bind(this);
this.playerHandler.setTarget(playerTarget);
// Setup remote player properties on setup
if (this.remotePlayer.isMuted) {
this.playerHandler.mute();
}
this.enableProgressBar(this.remotePlayer.canSeek);
// The remote player may have had a volume set from previous playback
var currentVolume = this.remotePlayer.volumeLevel * FULL_VOLUME_HEIGHT;
var p = document.getElementById('audio_bg_level');
p.style.height = currentVolume + 'px';
p.style.marginTop = -currentVolume + 'px';
// Show media_control
document.getElementById('media_control').style.opacity = 0.7;
this.hideFullscreenButton();
// If resuming a session, take the remote properties and continue the existing
// playback. Otherwise, load local content.
if (cast.framework.CastContext.getInstance().getCurrentSession().getSessionState() ==
cast.framework.SessionState.SESSION_RESUMED) {
console.log('Resuming session');
this.playerHandler.prepareToPlay();
// New media has been loaded so the previous ad markers should
// be removed.
this.removeAdMarkers();
this.updateAdMarkers();
} else {
this.playerHandler.load();
}
};
/**
* Callback when media is loaded in local player
*/
CastPlayer.prototype.onMediaLoadedLocally = function () {
var localPlayer = document.getElementById('video_element');
localPlayer.currentTime = this.currentMediaTime;
this.playerHandler.prepareToPlay();
};
/**
* Select a media content
* @param {number} mediaIndex A number for media index
*/
CastPlayer.prototype.selectMedia = function (mediaIndex) {
console.log('Media index selected: ' + mediaIndex);
this.currentMediaIndex = mediaIndex;
// Clear currentMediaInfo when playing content from the sender.
this.playerHandler.currentMediaInfo = undefined;
// Set video image
var vi = document.getElementById('video_image');
vi.src = MEDIA_SOURCE_ROOT + this.mediaContents[mediaIndex]['thumb'];
// Reset progress bar
var pi = document.getElementById('progress_indicator');
pi.style.marginLeft = '0px';
var progress = document.getElementById('progress');
progress.style.width = '0px';
let seekable_window = document.getElementById('seekable_window');
let unseekable_overlay = document.getElementById('unseekable_overlay');
seekable_window.style.width = PROGRESS_BAR_WIDTH;
unseekable_overlay.style.width = '0px';
// Stop timer and reset time displays
this.stopProgressTimer();
this.currentMediaTime = 0;
this.playerHandler.setTimeString(document.getElementById('currentTime'), 0);
this.playerHandler.setTimeString(document.getElementById('duration'), 0);
this.playerState = PLAYER_STATE.IDLE;
this.playerHandler.play();
};
/**
* Media seek function
* @param {Event} event An event object from seek
*/
CastPlayer.prototype.seekMedia = function (event) {
if (this.mediaDuration == null || (cast.framework.CastContext.getInstance().getCurrentSession() && !this.remotePlayer.canSeek)) {
console.log('Error - Not seekable');
return;
}
if (this.isLiveContent && !this.liveSeekableRange) {
console.log('Live content has no seekable range.')
return;
}
var position = parseInt(event.offsetX, 10);
var pi = document.getElementById('progress_indicator');
var progress = document.getElementById('progress');
let seekTime = 0;
let pp = 0;
let pw = 0;
if (event.currentTarget.id == 'progress_indicator') {
seekTime = parseInt(this.currentMediaTime + this.mediaDuration * position /
PROGRESS_BAR_WIDTH, 10);
pp = parseInt(pi.style.marginLeft, 10) + position;
pw = parseInt(progress.style.width, 10) + position;
} else {
seekTime = parseInt(position * this.mediaDuration / PROGRESS_BAR_WIDTH, 10);
pp = position;
pw = position;
}
if (this.playerState === PLAYER_STATE.PLAYING ||
this.playerState === PLAYER_STATE.PAUSED) {
this.currentMediaTime = seekTime;
progress.style.width = pw + 'px';
pi.style.marginLeft = pp + 'px';
}
if (this.isLiveContent) {
seekTime += this.mediaInfo.metadata.sectionStartTimeInMedia;
}
this.playerHandler.seekTo(seekTime);
};
/**
* Set current player volume
* @param {Event} mouseEvent
*/
CastPlayer.prototype.setVolume = function (mouseEvent) {
var p = document.getElementById('audio_bg_level');
var pos = 0;
if (mouseEvent.currentTarget.id === 'audio_bg_track') {
pos = FULL_VOLUME_HEIGHT - parseInt(mouseEvent.offsetY, 10);
} else {
pos = parseInt(p.clientHeight, 10) - parseInt(mouseEvent.offsetY, 10);
}
this.playerHandler.setVolume(pos);
};
/**
* Starts the timer to increment the media progress bar
*/
CastPlayer.prototype.startProgressTimer = function () {
this.stopProgressTimer();
// Start progress timer
this.timer = setInterval(this.incrementMediaTimeHandler, TIMER_STEP);
};
/**
* Stops the timer to increment the media progress bar
*/
CastPlayer.prototype.stopProgressTimer = function () {
if (this.timer) {
clearInterval(this.timer);
this.timer = null;
}
};
/**
* Increment media current time depending on remote or local playback
*/
CastPlayer.prototype.incrementMediaTime = function () {
// First sync with the current player's time
this.currentMediaTime = this.playerHandler.getCurrentMediaTime();
this.mediaDuration = this.playerHandler.getMediaDuration();
this.playerHandler.updateDurationDisplay();
if (this.mediaDuration == null || this.currentMediaTime < this.mediaDuration || this.isLiveContent) {
this.playerHandler.updateCurrentTimeDisplay();
this.updateProgressBarByTimer();
} else if (this.mediaDuration > 0) {
this.endPlayback();
}
};
/**
* Update progress bar and currentTime based on timer
*/
CastPlayer.prototype.updateProgressBarByTimer = function () {
var progressBar = document.getElementById('progress');
var pi = document.getElementById('progress_indicator');
// Live situation where the progress and duration is unknown.
if (this.mediaDuration == null) {
if (!this.isLiveContent) {
console.log('Error - Duration is not defined for a VOD stream.');
}
progressBar.style.width = '0px';
document.getElementById('skip').style.display = 'none';
pi.style.display = 'none';
let seekable_window = document.getElementById('seekable_window');
let unseekable_overlay = document.getElementById('unseekable_overlay');
seekable_window.style.width = '0px';
unseekable_overlay.style.width = '0px';
return;
} else {
pi.style.display = '';
}
if (isNaN(parseInt(progressBar.style.width, 10))) {
progressBar.style.width = '0px';
}
// Prevent indicator from exceeding the max width. Happens during
// short media when each progress step is large
var pp = Math.floor(PROGRESS_BAR_WIDTH * this.currentMediaTime / this.mediaDuration);
if (pp > PROGRESS_BAR_WIDTH) {
pp = PROGRESS_BAR_WIDTH;
} else if (pp < 0) {
pp = 0;
}
progressBar.style.width = pp + 'px';
pi.style.marginLeft = pp + 'px';
let seekable_window = document.getElementById('seekable_window');
let unseekable_overlay = document.getElementById('unseekable_overlay');
if (this.isLiveContent) {
if (this.liveSeekableRange) {
// Use the liveSeekableRange to draw the seekable and unseekable windows
let seekableMediaPosition = Math.max(this.mediaInfo.metadata.sectionStartTimeInMedia, this.liveSeekableRange.end) -
this.mediaInfo.metadata.sectionStartTimeInMedia;
let seekableWidth = Math.floor(PROGRESS_BAR_WIDTH * seekableMediaPosition / this.mediaDuration);
if (seekableWidth > PROGRESS_BAR_WIDTH) {
seekableWidth = PROGRESS_BAR_WIDTH;
} else if (seekableWidth < 0) {
seekableWidth = 0;
}
seekable_window.style.width = seekableWidth + 'px';
let unseekableMediaPosition = Math.max(this.mediaInfo.metadata.sectionStartTimeInMedia, this.liveSeekableRange.start) -
this.mediaInfo.metadata.sectionStartTimeInMedia;
let unseekableWidth = Math.floor(PROGRESS_BAR_WIDTH * unseekableMediaPosition / this.mediaDuration);
if (unseekableWidth > PROGRESS_BAR_WIDTH) {
unseekableWidth = PROGRESS_BAR_WIDTH;
} else if (unseekableWidth < 0) {
unseekableWidth = 0;
}
unseekable_overlay.style.width = unseekableWidth + 'px';
} else {
// Nothing is seekable if no liveSeekableRange
seekable_window.style.width = '0px';
unseekable_overlay.style.width = PROGRESS_BAR_WIDTH + 'px';
}
} else {
// Default to everything seekable
seekable_window.style.width = PROGRESS_BAR_WIDTH + 'px';
unseekable_overlay.style.width = '0px';
}
if (pp >= PROGRESS_BAR_WIDTH && !this.isLiveContent) {
this.endPlayback();
}
};
/**
* End playback. Called when media ends.
*/
CastPlayer.prototype.endPlayback = function () {
this.currentMediaTime = 0;
this.stopProgressTimer();
this.playerState = PLAYER_STATE.IDLE;
this.playerHandler.updateDisplay();
document.getElementById('play').style.display = 'block';
document.getElementById('pause').style.display = 'none';
};
/**
* @param {?number} timestamp Linux timestamp
* @return {?string} media time string. Null if time is invalid.
*/
CastPlayer.prototype.getMediaTimeString = function (timestamp) {
if (timestamp == undefined || timestamp == null) {
return null;
}
let isNegative = false;
if (timestamp < 0) {
isNegative = true;
timestamp *= -1;
}
let hours = Math.floor(timestamp / 3600);
let minutes = Math.floor((timestamp - (hours * 3600)) / 60);
let seconds = Math.floor(timestamp - (hours * 3600) - (minutes * 60));
if (hours < 10) hours = '0' + hours;
if (minutes < 10) minutes = '0' + minutes;
if (seconds < 10) seconds = '0' + seconds;
return (isNegative ? '-' : '') + hours + ':' + minutes + ':' + seconds;
};
/**
* @param {number} timestamp Linux timestamp
* @return {?string} ClockTime string. Null if time is invalid.
*/
CastPlayer.prototype.getClockTimeString = function (timestamp) {
if (!timestamp) return "0:00:00";
let date = new Date(timestamp * 1000);
let hours = date.getHours();
let minutes = date.getMinutes();
let seconds = date.getSeconds();
let ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12;
// Hour '0' should be '12'
hours = hours ? hours : 12;
minutes = ('0' + minutes).slice(-2);
seconds = ('0' + seconds).slice(-2);
let clockTime = hours + ':' + minutes + ':' + seconds + ' ' + ampm;
return clockTime;
};
/**
* Updates Ad markers in UI
*/
CastPlayer.prototype.updateAdMarkers = function () {
let castSession = cast.framework.CastContext.getInstance().getCurrentSession();
if (!castSession) return;
let media = castSession.getMediaSession();
if (!media) return;
let mediaInfo = media.media;
if (!mediaInfo) return;
let breaks = mediaInfo.breaks;
let contentDuration = mediaInfo.duration;
if (!breaks) {
return;
}
for (var i = 0; i < breaks.length; i++) {
let adBreak = breaks[i];
// Server-side stitched Ads (embedded) are skipped when the position is beyond
// the duration, so they shouldn't be shown with an ad marker on the UI.
if (adBreak.position > contentDuration && adBreak.isEmbedded) {
continue;
}
// Place marker if not already set in position
if (!document.getElementById('ad' + adBreak.position)) {
var div = document.getElementById('progress')
div.innerHTML += '<div class="adMarker" id="ad' + adBreak.position +
'" style="margin-left: ' +
this.adPositionToMargin(adBreak.position, contentDuration) + 'px"></div>';
}
}
};
/**
* Remove Ad markers in UI
*/
CastPlayer.prototype.removeAdMarkers = function () {
document.querySelectorAll('.adMarker').forEach(function (adMarker) {
adMarker.remove();
});
};
/**
* Position of the ad marker from the margin
*/
CastPlayer.prototype.adPositionToMargin = function (position, contentDuration) {
// Post-roll
if (position == -1) {
return PROGRESS_BAR_WIDTH;
}
// Client stitched Ads (not embedded) beyond the duration, will play at the
// end of the content.
if (position > contentDuration) {
return PROGRESS_BAR_WIDTH;
}
// Convert Ad position to margin
return (PROGRESS_BAR_WIDTH * position) / contentDuration;
};
/**
* Handle BREAK_CLIP_ID_CHANGED event
*/
CastPlayer.prototype.onBreakClipIdChanged = function () {
// Hide skip button when switching to a new breakClip
document.getElementById('skip').style.display = 'none';
};
document.getElementById('progress_indicator').draggable = true;
// Set up feature radio buttons
let noneRadio = document.getElementById('none');
noneRadio.onclick = function () {
ENABLE_LIVE = false;
ENABLE_ADS = false;
console.log("Features have been removed");
}
let adsRadio = document.getElementById('ads');
adsRadio.onclick = function () {
ENABLE_LIVE = false;
ENABLE_ADS = true;
console.log("Ads have been enabled");
}
let liveRadio = document.getElementById('live');
liveRadio.onclick = function () {
ENABLE_LIVE = true;
ENABLE_ADS = false;
console.log("Live has been enabled");
}
if (ENABLE_ADS) {
if (ENABLE_LIVE) {
console.error('Only one feature can be enabled at a time. Enabling ads.');
}
adsRadio.checked = true;
console.log("Ads are enabled");
} else if (ENABLE_LIVE) {
liveRadio.checked = true;
console.log("Live is enabled");
} else {
noneRadio.checked = true;
console.log("No features are enabled");
}
};
/**
* Add video thumbnails div's to UI for media JSON contents
*/
CastPlayer.prototype.addVideoThumbs = function () {
this.mediaContents = mediaJSON['categories'][0]['videos'];
var ni = document.getElementById('carousel');
var newdiv = null;
var divIdName = null;
for (var i = 0; i < this.mediaContents.length; i++) {
newdiv = document.createElement('div');
divIdName = 'thumb' + i + 'Div';
newdiv.setAttribute('id', divIdName);
newdiv.setAttribute('class', 'thumb');
newdiv.innerHTML =
'<img src="' + MEDIA_SOURCE_ROOT + this.mediaContents[i]['thumb'] +
'" class="thumbnail">';
newdiv.addEventListener('click', this.selectMedia.bind(this, i));
ni.appendChild(newdiv);
}
};
/**
* Makes human-readable message from chrome.cast.Error
* @param {chrome.cast.Error} error
* @return {string} error message
*/
CastPlayer.getErrorMessage = function (error) {
switch (error.code) {
case chrome.cast.ErrorCode.API_NOT_INITIALIZED:
return 'The API is not initialized.' +
(error.description ? ' :' + error.description : '');
case chrome.cast.ErrorCode.CANCEL:
return 'The operation was canceled by the user' +
(error.description ? ' :' + error.description : '');
case chrome.cast.ErrorCode.CHANNEL_ERROR:
return 'A channel to the receiver is not available.' +
(error.description ? ' :' + error.description : '');
case chrome.cast.ErrorCode.EXTENSION_MISSING:
return 'The Cast extension is not available.' +
(error.description ? ' :' + error.description : '');
case chrome.cast.ErrorCode.INVALID_PARAMETER:
return 'The parameters to the operation were not valid.' +
(error.description ? ' :' + error.description : '');
case chrome.cast.ErrorCode.RECEIVER_UNAVAILABLE:
return 'No receiver was compatible with the session request.' +
(error.description ? ' :' + error.description : '');
case chrome.cast.ErrorCode.SESSION_ERROR:
return 'A session could not be created, or a session was invalid.' +
(error.description ? ' :' + error.description : '');
case chrome.cast.ErrorCode.TIMEOUT:
return 'The operation timed out.' +
(error.description ? ' :' + error.description : '');
default:
return error;
}
};
let castPlayer = new CastPlayer();
window['__onGCastApiAvailable'] = function (isAvailable) {
if (isAvailable) {
castPlayer.initializeCastPlayer();
}
};