\
'
};
// compile templates
this.templates = {};
for (var template in templateHtml)
this.templates[template] = _.template(templateHtml[template]);
// instantiate video
this.createPlayer();
this.createNotifications();
this.loadSrt(this.options.defaultLanguage);
};
/**
* Check if a new question is available, adding it to the notifications container if so
*
*/
CS50.Video.prototype.checkQuestionAvailable = function() {
// make sure notifications container is given
if (!this.options.notificationsContainer)
return;
var player = this.player;
var $container = $(this.options.notificationsContainer).find('tbody');
// check if any of the given questions should be displayed at this timecode
var me = this;
_.each(this.options.questions, function(e, i) {
// question should be shown if timecodes match and it isn't already shown
if (e.timecode == Math.floor(player.getCurrentTime()) &&
!$container.find('tr[data-question-id="' + e.question.id + '"]').length) {
// don't take both actions on the same question
if (me.currentQuestion != e.question.id) {
// automatically go to the new question if user checked that box
if ($(me.options.notificationsContainer).find('#video50-notifications-auto').is(':checked'))
me.showQuestion(e.question.id)
// put question at the top of the list of available questions
else {
$container.prepend(me.templates.notification({
question: e
})).find('[rel=tooltip]').tooltip({
placement: 'right'
});
}
}
}
})
};
/**
* Create a new instance of the video player at the specified container
*
*/
CS50.Video.prototype.createPlayer = function() {
// create html for video player
var $container = $(this.options.playerContainer);
$container.html(this.templates.player({
defaultLanguage: this.options.defaultLanguage,
height: this.options.height,
srt: this.options.srt,
swf: this.options.swf,
title: this.options.title,
video: this.options.video,
width: this.options.width
}));
// create video player
var me = this;
this.player = new MediaElementPlayer(this.options.playerContainer + ' .video-player', {
timerRate: 500,
success: function (player, dom) {
// event handler for video moving forward
player.addEventListener('timeupdate', function(e) {
// check if a new question is available
me.checkQuestionAvailable();
// update highlight on the transcript
me.updateTranscriptHighlight();
}, false);
// start video immediately if autostart is enabled
if (me.options.autostart)
player.play();
// determine if browser is capable of variable playback speeds
var canAdjustPlayback = player.pluginType != 'flash';
// if playback rates are given, then display controls
if (me.options.playbackRates.length && canAdjustPlayback) {
// use explicit container if given, else simply put controls below video
if (me.options.playbackContainer)
$(me.options.playbackContainer).html(me.templates.playbackControls({
rates: me.options.playbackRates
}));
else {
me.options.playbackContainer = $(me.templates.playbackControls({
rates: me.options.playbackRates
}));
$container.after(me.options.playbackContainer);
}
// 1 is the default playback rate
var $playbackContainer = $(me.options.playbackContainer);
$playbackContainer.find('[data-rate="1"]').addClass('active');
// when playback button is changed, alter rate of video
$playbackContainer.on('click', '.btn-playback-rate', function(e) {
// highlight the current control and remove highlight from others
$(this).siblings().removeClass('active');
$(this).addClass('active');
// adjust video rate
me.player.media.playbackRate = parseFloat($(this).attr('data-rate'));
e.preventDefault();
return false;
});
}
}
});
// when back button is pressed, return to video
$container.on('click', '.btn-back', function(e) {
// hide button
$(this).fadeOut('medium');
// start video and flip back
me.player.play();
$container.find('.flip-container').removeClass('flipped');
// remove input
$('.video50-txt-answer').remove();
// fade video back in while flip is occurring for smoothness
setTimeout(function() {
$container.find('.video-container').fadeIn('medium');
}, 500);
});
};
/**
* Create a new instance of the notification area at the specified container
*
*/
CS50.Video.prototype.createNotifications = function() {
// build notifications container
var $container = $(this.options.notificationsContainer);
$container.html(this.templates.notifications());
// event handler for selecting a question to view
var me = this;
$container.on('click', 'a', function() {
// display question
var id = $(this).parents('[data-question-id]').attr('data-question-id');
me.showQuestion(id);
// remove selected question from list
$(this).tooltip('hide');
$(this).parents('tr').remove();
});
};
/**
* Load the specified SRT file
*
* @param lang Language to load
*
*/
CS50.Video.prototype.loadSrt = function(language) {
this.srtData = {};
var player = this.player;
var me = this;
if (this.options.srt[language]) {
$.get(this.options.srt[language], function(response) {
var timecodes = response.split(/\n\s*\n/);
// if transcript container is given, then build transcript
if (me.options.transcriptContainer) {
// create transcript container
$(me.options.transcriptContainer).html(me.templates.transcript({
srt: me.options.srt,
language: language
}));
// clear previous text
var $container = $(me.options.transcriptContainer).find('.video50-transcript-text');
$container.empty();
// iterate over each timecode
var n = timecodes.length;
for (var i = 0; i < n; i++) {
// split the elements of the timecode
var timecode = timecodes[i].split("\n");
if (timecode.length > 1) {
// extract time and content from timecode
var timestamp = timecode[1].split(" --> ")[0];
timecode.splice(0, 2);
var content = timecode.join(" ");
// if line starts with >> or [, then start a new line
if (content.match(/^(>>|\[)/))
$container.append('
');
// convert from hours:minutes:seconds to seconds
var time = timestamp.match(/(\d+):(\d+):(\d+)/);
var seconds = parseInt(time[1], 10) * 3600 + parseInt(time[2], 10) * 60 + parseInt(time[3], 10);
// add line to transcript
$container.append('' + content + '');
}
}
// when transcript language is changed, refresh srt data and captioning
$(me.options.transcriptContainer).on('click', '.video50-transcript-lang a[data-lang]', function() {
// refresh transcript
var lang = $(this).attr('data-lang');
me.loadSrt(lang);
// change language in player if captions have been turned on by user
if ($(me.options.playerContainer).find('.mejs-captions-selector input[type=radio]:checked').attr('value') != 'none')
$(me.options.playerContainer).find('.mejs-captions-selector input[value="' + lang + '"]').click();
});
// when captioning is changed, refresh srt data
$(me.options.playerContainer).on('click', '.mejs-captions-selector input[type=radio]', function() {
var lang = $(this).attr('value');
if (lang != 'none')
me.loadSrt(lang);
});
// when a line is clicked, seek to that time in the video
$container.on('click', 'a', function() {
// determine timecode associated with line
var time = $(this).attr('data-time');
if (time)
player.setCurrentTime(time);
});
// keep track of scroll state so we don't auto-seek the transcript when the user scrolls
me.disableTranscriptAutoSeek = false;
$(me.options.transcriptContainer).find('.video50-transcript-container').on('scrollstart', function() {
me.disableTranscriptAutoSeek = true;
});
$(me.options.transcriptContainer).find('.video50-transcript-container').on('scrollstop', function() {
me.disableTranscriptAutoSeek = false;
});
}
});
}
};
/**
* Callback for logging question data
*
* @param correct Whether or not the question was answered correctly
* @param data Additional data to be logged by server
*
*/
CS50.Video.prototype.renderCallback = function(correct, data) {
return true;
};
/**
* Show the question with the given ID
*
*/
CS50.Video.prototype.showQuestion = function(id) {
// determine question to show
var question = _.find(this.options.questions, function(e) { return e.question.id == id; });
if (question) {
// keep track of the current question
this.currentQuestion = id;
// flip video over to display question
if (question.mode == CS50.Video.QuestionMode.FLIP) {
// stop video so we can think, think, thiiiiiink
this.player.pause();
// remove existing panel questions
$('.video50-question .panel-close').click();
// clear previous question contents and events
var player = $(this.options.playerContainer);
var $container = $(this.options.playerContainer).find('.flip-question-container .question-content');
$container.empty().off();
// render question
question.question.render($container, question.question, CS50.Video.renderCallback);
// flip player to show question
$(this.options.playerContainer).find('.video-container').fadeOut('medium');
$(this.options.playerContainer).find('.flip-container').addClass('flipped');
// display back button
setTimeout(function() {
player.find('.btn-back').show();
}, 100);
}
// display question in the specified panel while video plays
else if (question.mode == CS50.Video.QuestionMode.PANEL) {
// remove existing flip questions
$('.video50-player .btn-back').click();
// clear previous question contents and events
var $container = $(this.options.questionContainer);
$container.empty().off();
// render question
$container.hide().html(this.templates.panelQuestion()).fadeIn('fast');
question.question.render($container.find('.question-content'), question.question, CS50.Video.renderCallback);
// when x in top-right corner is clicked, remove the question
$container.on('click', '.panel-close', function() {
$container.find('.video50-question').fadeOut('fast', function() {
// remove question controls
$('.video50-txt-answer').remove();
$(this).remove();
});
});
}
}
};
/**
* Highlight the line corresponding to the current point in the video in the transcript
*
*/
CS50.Video.prototype.updateTranscriptHighlight = function() {
var time = Math.floor(this.player.getCurrentTime());
var $container = $(this.options.transcriptContainer);
var $active = $container.find('[data-time="' + time + '"]');
// check if a new element should be highlighted
if ($active && $active.length) {
// remove all other highlights
$container.find('a').removeClass('highlight');
// add highlight to active element
$active.addClass('highlight');
// put the current element in the middle of the transcript if user is not scrolling
if (!this.disableTranscriptAutoSeek && $container.find('#video50-transcript-auto').is(':checked')) {
var top = $active.position().top - parseInt($container.height() / 2);
$container.find('.video50-transcript-container').animate({ scrollTop: top });
}
}
};