我正在开发一个YouTube章节播放器,并尝试使用上述功能突出显示当前播放章节。我正在使用getCurrentTime()
功能获取YouTube视频的当前播放时间,并且我在player.getCurrentTime()
功能上遇到控制台错误:
Uncaught TypeError: undefined is not a function for player.getCurrentTime();
如何解决此错误?
相关代码:
var values = [];
var contentDiv, chapterEl, parentEl;
var chapters = {};
var index;
function loadplayer(youtubeId){
$(document).ready(function() {
$.ajax({
type: "GET",
url: "ChapterVideo.xml",
dataType: "xml",
success: function(xml) {
$(xml).find("video[id='" + youtubeId + "'] chapter").each(function() {
var sTitle = $(this).find('name').text();
var sValue = parseFloat($(this).find('time').text());
values.push([sTitle, sValue]);
});
chapterplayer(values);
},
error: function() {
alert("An error occurred while processing XML file.");
}
});
});
function chapterplayer(values) {
for (index = 0; index < values.length; ++index) {
chapters[values[index][1]] = '<span style="display: inline-block; width: 88%; margin-top: 1.5%;">' + values[index][0] + '</span>';
}
//chapters[times] = '<span style="display: inline-block; width: 88%; margin-top: 1.5%;">' + names + '</span>';
// BEGIN_INCLUDE(namespace)
window.ChapterMarkerPlayer = {
// Inserts a new YouTube iframe player and chapter markers as a child of an
// existing HTML element on a page.
insert: function(params) {
// END_INCLUDE(namespace)
// We need to reserve 30px for the player's control bar when automatically sizing the player.
var YOUTUBE_CONTROLS_HEIGHT = 30;
// Assume a 9:16 (width:height) ratio when we need to calculate a player's height.
var PLAYER_HEIGHT_TO_WIDTH_RATIO = 11 / 16;
var DEFAULT_PLAYER_WIDTH = 400;
// BEGIN_INCLUDE(validation1)
// params contains the following required and optional parameter names and values:
// videoId: (required) The YouTube video id of the video to be embedded.
// chapters: (required) Mapping of times (seconds since the video's start) to chapter titles.
// width: (optional) The width of the embedded player. 400px is used by default.
// playerOptions: (optional) An object corresponding to the options that can be passed to the
// YT.Player constructor. See https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player
if (!('videoId' in params)) {
throw 'The "videoId" parameter must be set to the YouTube video id to be embedded.';
}
if (!('chapters' in params)) {
throw 'The "chapters" parameter must be set to the mapping of times to chapter titles.';
}
// END_INCLUDE(validation1)
// BEGIN_INCLUDE(time_sort)
var times = [];
for (var time in params.chapters) {
if (params.chapters.hasOwnProperty(time)) {
times.push(time);
}
}
// Sort the times numerically for display purposes.
// See https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort#Examples
times.sort(function(a, b) {
return a - b;
});
// END_INCLUDE(time_sort)
var width = params.width || DEFAULT_PLAYER_WIDTH;
if ('YT' in window && 'Player' in window.YT) {
// If the iframe player API is already available, proceed to loading the player using the API.
insertPlayerAndAddChapterMarkers(params);
} else {
// Load the API, and add a callback to the queue to load the player once the API is available.
if (!('onYouTubePlayerAPIReady' in window)) {
// BEGIN_INCLUDE(invoke_callbacks)
window.onYouTubePlayerAPIReady = function() {
for (var i = 0; i < window.ChapterMarkerPlayer.onYouTubePlayerAPIReadyCallbacks.length; i++) {
window.ChapterMarkerPlayer.onYouTubePlayerAPIReadyCallbacks[i]();
}
};
// END_INCLUDE(invoke_callbacks)
// BEGIN_INCLUDE(load_api)
// Dynamic <script> tag insertion will effectively load the iframe Player API on demand.
// We only want to do this once, so it's protected by the
// !('onYouTubePlayerAPIReady' in window) check.
var scriptTag = document.createElement('script');
// This scheme-relative URL will use HTTPS if the host page is accessed via HTTPS,
// and HTTP otherwise.
scriptTag.src = 'https://www.youtube.com/player_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(scriptTag, firstScriptTag);
// END_INCLUDE(load_api)
}
// BEGIN_INCLUDE(queue_callbacks)
// We need to handle the situation where multiple ChapterMarkerPlayer.insert() calls are made
// before the YT.Player API is loaded. We do this by maintaining an array of functions, each
// of which adds a specific player and chapters. The functions will be executed when
// onYouTubePlayerAPIReady() is invoked by the YT.Player API.
window.ChapterMarkerPlayer.onYouTubePlayerAPIReadyCallbacks.push(function() {
insertPlayerAndAddChapterMarkers(params);
});
// END_INCLUDE(queue_callbacks)
}
// BEGIN_INCLUDE(load_player)
// Calls the YT.Player constructor with the appropriate options to add the iframe player
// instance to a parent element.
// This is a private method that isn't exposed via the ChapterMarkerPlayer namespace.
function initializePlayer(containerElement, params) {
var playerContainer = document.createElement('div');
containerElement.appendChild(playerContainer);
// Attempt to use any custom player options that were passed in via params.playerOptions.
// Fall back to reasonable defaults as needed.
var playerOptions = params.playerOptions || {};
return new YT.Player(playerContainer, {
// Maintain a 16:9 aspect ratio for the player based on the width passed in via params.
// Override can be done via params.playerOptions if needed
height: playerOptions.height || width * PLAYER_HEIGHT_TO_WIDTH_RATIO + YOUTUBE_CONTROLS_HEIGHT,
width: playerOptions.width || width,
// Unless playerVars are explicitly provided, use a reasonable default of { autohide: 1 },
// which hides the controls when the mouse isn't over the player.
playerVars: playerOptions.playerVars || {
autohide: 1
},
videoId: params.videoId,
events: {
onReady: playerOptions.onReady,
onStateChange: playerOptions.onStateChange,
onPlaybackQualityChange: playerOptions.onPlaybackQualityChange,
onError: playerOptions.onError
}
});
}
// END_INCLUDE(load_player)
// BEGIN_INCLUDE(format_timestamp)
// Takes a number of seconds and returns a #h##m##s string.
function formatTimestamp(timestamp) {
var hours = Math.floor(timestamp / 3600);
var minutes = Math.floor((timestamp - (hours * 3600)) / 60);
var seconds = timestamp % 60;
var formattedTimestamp = (seconds < 10 ? '0' : '') + seconds;
if (minutes == 0) {
formattedTimestamp = "00:" + formattedTimestamp;
}
if (minutes > 0) {
formattedTimestamp = (minutes < 10 ? '0' : '') + minutes + ':' + formattedTimestamp;
}
if (hours > 0) {
formattedTimestamp = hours + 'h' + formattedTimestamp;
}
return formattedTimestamp;
}
// END_INCLUDE(format_timestamp)
// BEGIN_INCLUDE(add_chapter_markers)
// Adds a sorted list of chapters below the player. Each chapter has an onclick handler that
// calls the iframe player API to seek to a specific timestamp in the video.
// This is a private method that isn't exposed via the ChapterMarkerPlayer namespace.
function hightlightChapters(player) {
}
function addChapterMarkers(containerElement, player) {
// Initialize chapter markers for highlight
var active = 'background:url(http://www.russell.com/resources/images/play_btn_active.png) #5d5e5e no-repeat 3% center; color: #FFF;';
var ol = document.createElement('ol');
ol.setAttribute('class', 'chapter-list');
ol.setAttribute('style', 'width: ' + width + 'px');
containerElement.appendChild(ol);
contentDiv = document.querySelector('.content-detail');
parentEl = contentDiv.parentElement;
chapterEl = parentEl.querySelector('.chapter-list');
parentEl.insertBefore(contentDiv, chapterEl);
for (var i = 0; i < times.length; i++) {
var time = times[i];
var chapterTitle = params.chapters[time];
var liElements = document.getElementsByTagName('li'),
x;
var li = document.createElement('li');
li.setAttribute('data-time', time);
li.innerHTML = chapterTitle + "<span style='float: right; margin-top: 1.5%;'>" + formatTimestamp(time) + "</span>";
li.setAttribute('id', time);
//player.playVideo();
setInterval(function() {
var curtime = player.getCurrentTime();
document.getElementById('timer').innerHTML = player.getCurrentTime();
for (index = 0; index < values.length; ++index) {
if (curtime > values[index][1] && curtime < values[index+1][1]) {
//remove all the in-line styles from the elements
for (x = 0; x < liElements.length; x++) {
liElements[x].setAttribute('style', '');
};
document.getElementById(values[index][1]).setAttribute("style", active);
}else if (curtime > values[values.length-1][1]) {
//remove all the in-line styles from the elements
for (x = 0; x < liElements.length; x++) {
liElements[x].setAttribute('style', '');
};
document.getElementById(values[values.length-1][1]).setAttribute("style", active);
}
}
}, 1000); //polling frequency in miliseconds
li.onclick = function() {
player.playVideo();
// 'this' will refer to the element that was clicked
//get the ID of the clicked element
document.getElementById('timer').innerHTML = player.getCurrentTime();
var chapId = this.getAttribute('id');
//player.playVideo();
//remove all the in-line styles from the elements
for (x = 0; x < liElements.length; x++) {
liElements[x].setAttribute('style', '');
};
var d = document.getElementById(chapId);
d.setAttribute("style", active);
player.seekTo(this.getAttribute('id'));
};
ol.appendChild(li);
}
}
// END_INCLUDE(add_chapter_markers)s
// Convenience method to call both initializePlayer and addChapterMarkers.
// This is a private method that isn't exposed via the ChapterMarkerPlayer namespace.
function insertPlayerAndAddChapterMarkers(params) {
// BEGIN_INCLUDE(validation2)
var containerElement = document.getElementById(params.container);
if (!containerElement) {
throw 'The "container" parameter must be set to the id of a existing HTML element.';
}
// END_INCLUDE(validation2)
var player = initializePlayer(containerElement, params);
addChapterMarkers(containerElement, player);
console.log(player);
}
},
// BEGIN_INCLUDE(callback_array)
// This is used to keep track of the callback functions that need to be invoked when the iframe
// API has been loaded. It avoids a race condition that would lead to issues if multiple
// ChapterMarkerPlayer.insert() calls are made before the API is available.
onYouTubePlayerAPIReadyCallbacks: []
// END_INCLUDE(callback_array)
}
ChapterMarkerPlayer.insert({
container: 'captions-session-player',
videoId: youtubeId + '?wmode=opaque&theme=light&autohide=0&modestbranding=1&rel=0&autoplay=0',
width: 469,
chapters: chapters
});
//values.length=0;
}
}
控制台日志结果
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
chapter_marker_player.js:220 X {j: rb, d: iframe#widget2, t: div, closure_uid_350236415: 1, L: 1…}
53 chapter_marker_player.js:223 Uncaught TypeError: undefined is not a function