/**
* Scalar
* Copyright 2013 The Alliance for Networking Visual Culture.
* http://scalar.usc.edu/scalar
* Alliance4NVC@gmail.com
*
* Licensed under the Educational Community License, Version 2.0
* (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.osedu.org/licenses /ECL-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
/**
* @projectDescription The MediaElement plug-in creates and manages the interface for a Scalar media resource.
* Scalar is a project of The Alliance for Networking Visual Culture (http://scalar.usc.edu).
* @author Erik Loyer
* @version 1.1
*/
var mediaElementUniqueID = 0;
var youTubeMediaElementViews = [];
var soundCloudInitialized = false;
var pendingDeferredMedia = {};
var imagesWithAnnotations = {};
/**
* Get YouTube ID from various YouTube URL
* @author: takien
* @url: http://takien.com
* For PHP YouTube parser, go here http://takien.com/864
*/
function YouTubeGetID(url){
var ID = '';
url = url.replace(/(>|<)/gi,'').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
if(url[2] !== undefined) {
ID = url[2].split(/[^0-9a-z_-]/i);
ID = ID[0];
}
else {
ID = url;
}
return ID;
}
(function($) {
/**
* The base class for the plug-in.
* @constructor
*
* @param {Object} link An object encapsulating the original hyperlink and other data from which the media element is being generated.
* @param {Object} options An object containing other relevant data for the media element.
*/
var MediaElement = function(link, options) {
this.model = new $.MediaElementModel(options, link); // instance of the model
this.link = link; // the original hyperlink
this.controller = new $.MediaElementController(this.model, this.link); // instance of the main controller
this.view = new $.MediaElementView(this.model, this.controller, this.link); // instance of the main view
this.model.id = mediaElementUniqueID++; // unique id for the instance
this.controller.view = this.view;
if (options['auto_width']) $(this.link).data('auto_width', true); // To pass to media creation prototypes
var me = this;
/**
* Basic initalization.
*/
this.setup = function() {
this.model.init($(link), options);
this.controller.loadMetadata();
}
/**
* Returns a jQuery object containing the media player.
* @return The media player object.
*/
this.getEmbedObject = function() {
return(this.model.element);
};
/**
* Set the height of the mediaContainer and child elements in such a way that they are flexible when 'auto_width' is set to true
* Call this after calling this.getEmbedObject() ... takes the place of a "creation complete" event, which I don't think exists in JS?
* @return null
*/
this.finishAutoWidth = function() {
$(this.model).trigger('finishAutoWidth');
}
/**
* Returns the function to be called after the media player is added to the page.
* @return The callback function.
*/
this.getCallback = function() {
return this.callback;
}
/**
* Takes any action needed after the player has been added to the page.
*/
this.callback = function() {
}
/**
* Returns a string describing the type of media being presented.
* @return The content type string.
*/
// TODO: I don't this is valid anymore, should be this.model.mediaSource.extensions? (returns array) -- Craig
this.getContentType = function() {
return this.model.extension;
}
/**
* Starts playback of time-based media.
*/
this.play = function() {
this.view.play();
}
/**
* Pauses playback of time-based media.
*/
this.pause = function() {
this.view.pause();
}
/**
* Seeks to a given time position within a piece of time-based media.
*
* @param {Object} data Data representing the seek point.
*/
this.seek = function(data) {
// if the data has a url property, then we assume it's an annotation node that could represent
// a temporal, spatial, or 3D annotation
if ( data != null ) {
if (data.properties) {
this.view.seek(data);
// otherwise, we assume its a number, string, or JSON representing the start time of a temporal annotation
// or the position data of a 3D annotation
} else {
this.view.mediaObjectView.seek(data);
}
}
}
/**
* Sends a message to the media element.
*
* @param {String} message Message to be sent.
*/
this.sendMessage = function(message) {
if (this.view.mediaObjectView.sendMessage) {
this.view.mediaObjectView.sendMessage(message);
}
}
/**
* For time-based media, returns the current playback time.
* @return Returns the current playback time for temporal media in seconds.
*/
this.getCurrentTime = function() {
return this.view.getCurrentTime();
}
this.getPosition3D = function() {
return this.view.getPosition3D();
}
this.handleAnnotationsUpdated = function() {
return this.view.handleAnnotationsUpdated();
}
/**
* Returns true if the link passed to the plugin should be turned into a media player.
* @return Returns true if the link is to a file that's been imported into Scalar.
*/
this.is_valid = function() {
return (this.link.data('meta') && this.link.data('meta').length);
}
/**
* Returns true if the media element instance is currently playing (time-based media only).
* @return Returns true if the time-based media is currently playing.
*/
this.is_playing = function() {
return this.view.isPlaying();
}
this.setup();
}
/**
* Plug-in setup.
*
* @param {Object} options An object containing relevant data for the media element.
*/
$.fn.mediaelement = function(options) {
return this.each(function() {
// check to see if we've been passed a link to a Scalar media file
var element = $(this);
if (element.data('meta') && element.data('meta').length) {
// don't create a new instance of the plug-in class if one already exists on this element
if (element.data('mediaelement')) return;
var mediaelement = new MediaElement(this, options);
// store the class in the data of the element itself
element.data('mediaelement', mediaelement);
// broadcast an event to the page once the element is created
$('body').trigger( 'mediaElementComplete', [$(this)] );
}
});
};
/**
* Model for the plug-in.
* @constructor
*/
jQuery.MediaElementModel = function(options, link) {
this.options = options; // miscellaneous configuration data
this.element = $('
'); // jQuery object that will contain the player
this.mediaelement_dir = widgets_uri+'/mediaelement/'; // url of the mediaelement directory
this.base_dir = options.base_dir; // url of the current book
this.seek = options.seek; // initial seek position (optional)
this.seekAnnotation = null; // when doing an inital seek, this is the destination
this.node = null; // the Scalar node for the media
this.isChromeless = options.chromeless; // should the media be rendered in 'chromeless' style?
$('body').trigger('mediaElement_wrapperCreated', [this.element]);
/**
* Initializes the model.
*
* @param {Object} link An object encapsulating the original hyperlink and other data from which the media element is being generated.
* @param {Object} options An object containing other relevant data for the media element.
*/
jQuery.MediaElementModel.prototype.init = function(link, options) {
this.link = link; // encapsulates the original hyperlink and other data
this.sidebarWidth = 270; // width of the annotation sidebar
if ( this.options.autoplay == undefined ) {
this.options.autoplay = false;
}
// path to the media file
this.path = (this.link.data('path')) ? this.link.data('path') : '';
this.path = (this.path.indexOf('://')==-1) ? scalarapi.model.urlPrefix + this.path : this.path;
this.path = this.path.replace(/ /g, "%20").replace(/#/g, "%23"); // Simple urlencode for annotorious
// Scalar url for the media
this.meta = this.link.data('meta');
this.meta = (this.meta.indexOf('://')==-1) ? scalarapi.model.urlPrefix + this.meta : this.meta;
this.meta = scalarapi.model.urlPrefix.indexOf('https://') != -1 ? this.meta.replace('http://','https://') : this.meta;
// filename of the media
this.filename = scalarapi.basename(this.path);
this.filename = this.filename.replace(/ /g, '_');
this.filename = this.filename.replace(/%20/g, '_');
this.filename = this.filename.replace(/[^a-zA-Z 0-9-_]+/g, '');
// if height has been specified, assume the container has a horizontal layout
if (this.options.height) {
this.containerLayout = "horizontal";
// if width has been specified, assume the container has a vertical layout
} else if (this.options.width) {
this.containerLayout = "vertical";
}
}
}
/**
* Controller for the plug-in.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} link The hyperlink from which the mediaelement originated.
*/
jQuery.MediaElementController = function(model, link) {
var me = this;
this.model = model; // instance of the model
this.link = link; // original HTML node
/**
* Loads metadata for the media.
*/
jQuery.MediaElementController.prototype.loadMetadata = function() {
// don't load metadata if we're in cantaloupe (unless specifically requested using the "solo' option)
// (only cantaloupe pages should contain the "article" element)
//if (( $.find( 'article' ).length == 0 ) || this.model.options.solo ) {
if ( this.model.options.getRelated ) {
// get all relationships
if (scalarapi.loadNode(this.model.meta.substr(scalarapi.model.urlPrefix.length), true, this.handleMetadata, null, 1, true) == 'loaded') this.handleMetadata();
} else {
// get annotations only
if (scalarapi.loadNode(this.model.meta.substr(scalarapi.model.urlPrefix.length), true, this.handleMetadata, null, 2, false, 'annotation,tag') == 'loaded') this.handleMetadata();
}
/*} else {
// we need the delay so the plugin has the chance to make itself available to the page
setTimeout( this.handleMetadata, 10 );
}*/
}
/**
* Handles metadata load.
*
* @param {Object} json JSON data comprising the media metadata.
*/
jQuery.MediaElementController.prototype.handleMetadata = function(json) {
me.model.node = scalarapi.getNode( me.model.meta );
if ( me.model.node != null ) {
me.model.path = me.model.node.current.sourceFile;
me.model.mediaSource = me.model.node.current.mediaSource;
me.view.beginSetup();
}
$('body').trigger('mediaElementMetadataHandled', [$(link)]);
}
/**
* Given the dimensions of a source rectangle, and a destination rectangle, returns an object containing the new dimensions of the source rectangle such that it will fit within the destination rectangle.
*
* @property {Number} sourceWidth Width of the source rectangle.
* @property {Number} sourceHeight Height of the source rectangle.
* @property {Number} destWidth Width of the destination rectangle.
* @property {Number} destHeight Height of the destination rectangle.
* @return An object containing the new source rectangle dimensions.
*/
jQuery.MediaElementController.prototype.getFittedDimensions = function(sourceWidth, sourceHeight, destWidth, destHeight) {
var fittedWidth;
var fittedHeight;
var sourceAspectRatio = sourceWidth / sourceHeight;
var destAspectRatio = destWidth / destHeight;
if (destAspectRatio < sourceAspectRatio) {
fittedWidth = destWidth;
fittedHeight = destWidth / sourceAspectRatio;
} else {
fittedHeight = destHeight;
fittedWidth = destHeight * sourceAspectRatio;
}
return {width:fittedWidth, height:fittedHeight};
}
/**
* Toggles the annotation viewer open and closed.
*/
jQuery.MediaElementController.prototype.toggleAnnotations = function() {
this.view.toggleAnnotations();
}
}
/**
* Root view for the plug-in.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} controller Instance of the primary controller.
*/
jQuery.MediaElementView = function(model, controller, link) {
var me = this;
this.link = link; // original HTML node
this.model = model; // instance of the model
this.controller = controller; // instance of the primary controller
this.annotationsVisible = false; // are annotations currently visible?
this.intervalId = null; // id for timer interval
this.playingSeekAnnotation = false; // are we currently playing the annotation that we initially seeked to?
this.containerDim = {x:0, y:0}; // maximum dimensions of the media container
this.minContainerDim = {x:0, y:0}; // minimum dimensions of the media container
this.intrinsicDim = {x:0, y:0}; // intrinsic dimensions of the media
this.resizedDim = {x:0, y:0}; // resized dimensions of the media
this.mediaMargins = {horz:0, vert:0}; // margin between media and its container
this.gutterSize = 5; // size of gutter between media and container
this.controllerOffset = 0; // size of controller for media
this.controllerOnly = false; // does the media lack a visual display?
this.controllerHeight = 0; // height of a controller-only container
this.initialContainerWidth = 0; // initial width of the media container
this.overrideAutoSeek = false; // whether we should override the automatic seek to a specific annotation
this.annotationTimerRunning = false; // true if the timer that checks for annotation seek success is running
this.seekAttemptCount = 0; // number of times we've tried to seek
this.cachedPlayCommand = false; // if true, then we should play after a successful seek
this.currentAnnotation = null; // currently active annotation
this.native_full = true; // whether media is set to full in native size
/**
* Starts the process of initializing the view.
*/
jQuery.MediaElementView.prototype.beginSetup = function() {
/**
* Create a promise for any additional media libraries that must be loaded
* This is done to ensure that any additional library is loaded only once, as if we simply pass getScripts as promises,
* the way that the media elements are created will cause the library to load more than once upon media element
* of the same type that immediately follows - by deferring all media element functions on those elements until we are
* sure that the appropriate library is loaded, we can batch them together rather than possibly expend extra bandwidth.
*/
//Declare a self-resolving promise. This will be replaced if we need to wait to load a library
var promise = $.Deferred(function( deferred ){
$( deferred.resolve );
});
//We can only really detect libraries needed if the browser can support this type - otherwise, it will become an invalid media type later on
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] !== null ) {
var player;
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] != null ) {
player = this.model.mediaSource.browserSupport[scalarapi.scalarBrowser].player;
}
//QuickTime media elements need to load the quicktime library
if(typeof QT_GenerateOBJECTText_XHTML === 'undefined' && player == 'QuickTime'){
//If this is the first quicktime media element, then we need to initialize a list of promises and then start loading the library
if(typeof pendingDeferredMedia.QuickTime == 'undefined'){
pendingDeferredMedia.QuickTime = [];
$.getScript(widgets_uri+'/mediaelement/AC_QuickTime.js',function(){
//We have Quicktime loaded, so iterate through each deferred object and resolve it.
for(var i = 0; i < pendingDeferredMedia.QuickTime.length; i++){
pendingDeferredMedia.QuickTime[i].resolve();
}
});
}
//Here we make our promise and add it to the list of Quicktime deferred objects
promise = $.Deferred();
pendingDeferredMedia.QuickTime.push(promise);
//Below we have the same structure for OpenSeadragon and FlowPlayer. Additional media libraries may be added here.
}else if(typeof OpenSeadragon === 'undefined' && this.model.mediaSource.contentType == 'tiledImage'){
if(typeof pendingDeferredMedia.OpenSeadragon == 'undefined'){
pendingDeferredMedia.OpenSeadragon = [];
$.getScript(widgets_uri+'/mediaelement/openseadragon/openseadragon.min.js',function(){
for(var i = 0; i < pendingDeferredMedia.OpenSeadragon.length; i++){
pendingDeferredMedia.OpenSeadragon[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.OpenSeadragon.push(promise);
}else if(typeof Mirador === 'undefined' && this.model.mediaSource.contentType == 'manifest'){
if(typeof pendingDeferredMedia.Mirador == 'undefined'){
pendingDeferredMedia.Mirador = [];
$.getScript(widgets_uri+'/mediaelement/mirador.min.js',function(){
for(var i = 0; i < pendingDeferredMedia.Mirador.length; i++){
pendingDeferredMedia.Mirador[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.Mirador.push(promise);
}else if(typeof Hls === 'undefined' && player == 'HLS'){
if(typeof pendingDeferredMedia.Hls == 'undefined'){
pendingDeferredMedia.Hls = [];
$.when(
$.ajax({
url: widgets_uri+'/mediaelement/hls.min.js',
dataType: "script",
cache: true,
})
).then(function(){
for(var i = 0; i < pendingDeferredMedia.Hls.length; i++){
pendingDeferredMedia.Hls[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.Hls.push(promise);
}else if(typeof SC === 'undefined' && this.model.mediaSource.contentType == 'audio' && this.model.mediaSource.name == 'SoundCloud'){
if(typeof pendingDeferredMedia.SoundCloud == 'undefined'){
pendingDeferredMedia.SoundCloud = [];
$.when(
$.getScript('https://connect.soundcloud.com/sdk/sdk-3.3.2.js'),
$.getScript('https://w.soundcloud.com/player/api.js')
).then(function(){
for(var i = 0; i < pendingDeferredMedia.SoundCloud.length; i++){
pendingDeferredMedia.SoundCloud[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.SoundCloud.push(promise);
}else if(typeof THREE === 'undefined' && player == 'Threejs'){
if(typeof pendingDeferredMedia.Threejs == 'undefined'){
pendingDeferredMedia.Threejs = [];
//Because TrackballControls and STLLoader immediately require THREE to be set,
//we will first load three.min.js to make sure it is defined, then
//asynchronously load the other two files.
$.getScript(widgets_uri+'/mediaelement/three.min.js',function(){
$.when(
$.getScript(widgets_uri+'/mediaelement/TrackballControls.js'),
$.getScript(widgets_uri+'/mediaelement/STLLoader.js')
).then(function(){
for(var i = 0; i < pendingDeferredMedia.Threejs.length; i++){
pendingDeferredMedia.Threejs[i].resolve();
}
});
});
}
promise = $.Deferred();
pendingDeferredMedia.Threejs.push(promise);
}else if(typeof Vimeo === 'undefined' && this.model.mediaSource.contentType == 'video' && this.model.mediaSource.name == 'Vimeo'){
if(typeof pendingDeferredMedia.Vimeo == 'undefined'){
pendingDeferredMedia.Vimeo = [];
$.when(
$.getScript(widgets_uri+'/mediaelement/player.js')
).then(function(){
for(var i = 0; i < pendingDeferredMedia.Vimeo.length; i++){
pendingDeferredMedia.Vimeo[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.Vimeo.push(promise);
}else if(typeof Prism == 'undefined' && this.model.mediaSource.contentType == 'document' && (this.model.mediaSource.name == 'SourceCode' || scalarapi.getQueryVars( this.model.path ).lang != null)){
if(typeof pendingDeferredMedia.Prism == 'undefined'){
pendingScripts = 0;
pendingDeferredMedia.Prism = [];
$.when(
$.getScript(widgets_uri+'/mediaelement/prism.js')
).then(function(){
var doc = document;
var approot = $('link#approot').attr('href');
var cssLink = document.createElement("link");
cssLink.href = approot+'views/widgets/mediaelement/prism.css';
cssLink.rel = "stylesheet";
cssLink.type = "text/css";
doc.body.appendChild(cssLink);
for(var i = 0; i < pendingDeferredMedia.Prism.length; i++){
pendingDeferredMedia.Prism[i].resolve();
}
});
}
promise = $.Deferred();
pendingScripts++;
pendingDeferredMedia.Prism.push(promise);
}else if((typeof google == 'undefined' || typeof google.maps == 'undefined') && this.model.mediaSource.name == 'KML'){
if(typeof pendingDeferredMedia.GoogleMaps == 'undefined'){
pendingScripts = 0;
pendingDeferredMedia.GoogleMaps = [];
$.when(
$.getScript('https://maps.googleapis.com/maps/api/js?callback=initGoogleMap&key=' + $('link#google_maps_key').attr('href'))
).then(function(){
for(var i = 0; i < pendingDeferredMedia.GoogleMaps.length; i++){
pendingDeferredMedia.GoogleMaps[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.GoogleMaps.push(promise);
}else if(player == 'native' && $('.book-title').children('[data-semantic-annotation-tool]').length && !$.isFunction($.fn.annotate)){
if(typeof pendingDeferredMedia.Waldorf == 'undefined'){
pendingDeferredMedia.Waldorf = [];
var waldorfLocation = $('link#approot').attr('href') + 'views/widgets/waldorf/';
$.when(
$.getScript(waldorfLocation + 'jquery-ui-1.12.1.custom/jquery-ui.min.js'),
$.getScript(waldorfLocation + 'annotator-frontend-scalar.js')
).then(function(){
for(var i = 0; i < pendingDeferredMedia.Waldorf.length; i++){
pendingDeferredMedia.Waldorf[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.Waldorf.push(promise);
} else if (typeof arcgis == 'undefined' && this.model.mediaSource.name == 'ArcGIS WebScene'){
if(typeof pendingDeferredMedia.ArcGIS == 'undefined'){
pendingScripts = 0;
pendingDeferredMedia.ArcGIS = [];
$('', {
rel: 'stylesheet',
type: 'text/css',
href: 'https://js.arcgis.com/4.24/esri/themes/light/main.css'
}).appendTo('head');
var loadArcGIS = function() {
var deferred = $.Deferred();
var script = document.createElement('script');
script.onload = function() {
deferred.resolve();
}
script.src = 'https://js.arcgis.com/4.24/';
document.head.appendChild(script);
return deferred.promise();
}
$.when(
loadArcGIS()
).then(function(){
for(var i = 0; i < pendingDeferredMedia.ArcGIS.length; i++){
pendingDeferredMedia.ArcGIS[i].resolve();
}
});
}
promise = $.Deferred();
pendingDeferredMedia.ArcGIS.push(promise);
}
}
$.when(promise).then($.proxy(function(){
this.parseMediaType();
// don't parse annotations if this is an image and we're in the annotation editor (in that case, the annotation editor
// will take care of displaying the annotations)
if ((this.model.mediaSource.contentType != 'image') || (document.location.href.indexOf('.annotation_editor') == -1)) {
this.parseAnnotations();
}
this.header = $('').appendTo(this.model.element);
this.annotationSidebar = $('').appendTo(this.model.element);
this.mediaContainer = $('').appendTo(this.model.element);
if (this.model.mediaSource.name == "image") this.spatialAnnotation = $('
').appendTo(this.mediaContainer);
this.footer = $('').appendTo(this.model.element);
this.initialContainerWidth = parseInt(this.mediaContainer.width());
if ( this.model.isChromeless ) {
this.gutterSize = 0;
}
this.populateHeader();
this.populateFooter();
this.calculateContainerSize();
this.populateSidebar();
this.populateContainer();
if ('undefined'!=typeof(this.mediaObjectView)) {
// Native YouTube player requires that we download API code first
if (this.model.mediaSource.name == "YouTube") {
// check to see if another mediaelement instance has already downloaded the API
me.foundYouTubeAPI = false;
$('script').each(
function() {
if (this.src.indexOf('www.youtube.com') != -1) {
me.foundYouTubeAPI = true;
}
}
);
// if not, then do so, delay creation of the media object, and create function which
// will create all the YouTube instances once the API is ready
if (!me.foundYouTubeAPI) {
youTubeMediaElementViews.push(this);
// load the API
var tag = document.createElement('script');
tag.src = "//www.youtube.com/player_api";
//tag.src = "https://www.youtube.com/iframe_api"; // needed only if testing locally
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// set up function to handle API arrival
window['onYouTubePlayerAPIReady'] = function() {
var i;
var n = youTubeMediaElementViews.length;
// loop through all the waiting instances and create each one
for (i=0; i
Media
';
tabs += '
Description
';
tabs += '
Annotations
';
tabs += '
Metadata
';
tabs += '
Permalink
';
tabs += '
Popout
';
tabs += '';
this.header.addClass('mediaElementHeaderWithNavBar');
this.header.append(tabs);
// Media button: maximize the media
this.header.find('#max_file_button').on('click', function() {
$(this).parent().parent().find('span').removeClass('downArrow');
$(this).parent().parent().find('span').addClass('noArrow');
$(this).parent().parent().find('li').removeClass('sel');
$(this).parent().removeClass('noArrow');
$(this).parent().addClass('downArrow');
$(this).addClass('sel');
if (me.annotationsVisible) {
me.annotationsVisible = false;
me.updateSidebarDisplay();
}
});
// Description button: open the title|description panel
this.header.find('#max_desc_button').on('click', function() {
$(this).parent().parent().find('span').removeClass('downArrow');
$(this).parent().parent().find('span').addClass('noArrow');
$(this).parent().parent().find('li').removeClass('sel');
$(this).parent().removeClass('noArrow');
$(this).parent().addClass('downArrow');
$(this).addClass('sel');
if (!me.annotationsVisible) {
me.annotationsVisible = true;
me.updateSidebarDisplay();
}
var sidebar = $(this).parents('.mediaelement:first').children('.mediaElementAnnotationSidebar');
$(sidebar).find('.mediaElementSidebarDesc').show();
$(sidebar).find('.mediaElementSidebarDesc').removeAttr('padding');
$(sidebar).find('.mediaElementSidebarMeta').hide();
$(sidebar).find('.mediaElementSidebarMeta').removeAttr('padding');
$(sidebar).find('.annotationChip').hide();
});
// Annotation button: open the annotation panel
this.header.find('#max_anno_button').on('click', function() {
$(this).parent().parent().find('span').removeClass('downArrow');
$(this).parent().parent().find('span').addClass('noArrow');
$(this).parent().parent().find('li').removeClass('sel');
$(this).parent().removeClass('noArrow');
$(this).parent().addClass('downArrow');
$(this).addClass('sel');
if (!me.annotationsVisible) {
me.annotationsVisible = true;
me.updateSidebarDisplay();
}
var sidebar = $(this).parents('.mediaelement:first').children('.mediaElementAnnotationSidebar');
$(sidebar).find('.mediaElementSidebarDesc').hide();
$(sidebar).find('.mediaElementSidebarDesc').removeAttr('padding');
$(sidebar).find('.mediaElementSidebarMeta').hide();
$(sidebar).find('.mediaElementSidebarMeta').removeAttr('padding');
$(sidebar).find('.annotationChip').show();
});
// Metadata button: open the meta panel
this.header.find('#max_meta_button').on('click', function() {
$(this).parent().parent().find('span').removeClass('downArrow');
$(this).parent().parent().find('span').addClass('noArrow');
$(this).parent().parent().find('li').removeClass('sel');
$(this).parent().removeClass('noArrow');
$(this).parent().addClass('downArrow');
$(this).addClass('sel');
if (!me.annotationsVisible) {
me.annotationsVisible = true;
me.updateSidebarDisplay();
}
var sidebar = $(this).parents('.mediaelement:first').children('.mediaElementAnnotationSidebar');
$(sidebar).find('.mediaElementSidebarDesc').hide();
$(sidebar).find('.mediaElementSidebarDesc').removeAttr('padding');
$(sidebar).find('.mediaElementSidebarMeta').show();
$(sidebar).find('.mediaElementSidebarMeta').removeAttr('padding');
$(sidebar).find('.annotationChip').hide();
});
// Permalink button: take user to file page
this.header.find('#max_perm_button').data('meta',this.model.meta).on('click', function() {
window.location.href = $(this).data('meta');
});
// Source button: open the file in a new browser window
this.header.find('#max_popout_button').data('path',this.model.path).on('click', function() {
window.open($(this).data('path'), 'popout', 'width='+(parseInt($(window).width())-100)+',height='+(parseInt($(window).height())-100));
});
// Close button: close the maximize panel
var close_link = $('');
var close_span = $('');
close_span.append(close_link);
this.header.append(close_span);
close_link.on('click', function() {
$(this).parents('.maximize').remove();
$('.vert_slots').find('.slot').find('object, embed').css('visibility','visible');
});
$slot.find('.mediaElementAlignRight').remove();
// makes the description tab the default on open
this.header.find('#max_desc_button').trigger('click');
// Simple header
} else {
this.header.append('
'+this.model.node.getDisplayTitle()+'
');
if (this.model.node.current.publisher != null) {
this.header.find('span').append(' / '+this.model.node.current.publisher);
}
if (this.model.node.current.contributor != null) {
this.header.find('span').append(' / '+this.model.node.current.contributor);
}
if (this.model.node.current.source != null) {
this.header.find('span').append(' / '+this.model.node.current.source);
}
// toggle annotation display
if (this.model.containerLayout == "horizontal") {
var cntrllr = this.controller;
this.annotationLink = $('Annotations');
this.annotationLink.data('cntrllr', cntrllr);
this.annotationLink.on('click', function() {
$(this).data('cntrllr').toggleAnnotations();
});
var temp = $('');
this.annotationLink = temp.append(this.annotationLink);
this.annotationLink.appendTo(this.header);
}
}
}
}
/**
* Populates the media container element with content.
*/
jQuery.MediaElementView.prototype.populateContainer = function() {
switch (this.model.containerLayout) {
case "horizontal":
if ((this.mediaContainer.closest('.slot').length > 0) || (this.model.options.header == 'nav_bar')) {
// only set the height of the container if we're in a slot manager
if (model.options.width == null) this.mediaContainer.height(this.containerDim.y);
}
break;
case "vertical":
this.mediaContainer.css('max-height', this.containerDim.y);
break;
}
var playerIsNative = false;
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] != null ) {
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser].player == 'native' ) {
playerIsNative = true;
}
}
if ((this.model.mediaSource.contentType == 'image') && playerIsNative && (this.mediaObjectView.annotations != null)) {
this.mediaObjectView.setupAnnotations(this.mediaObjectView.annotations);
}
}
/**
* Populates the sidebar element with content.
*/
jQuery.MediaElementView.prototype.populateSidebar = function() {
if (this.model.containerLayout == "horizontal") {
this.annotationSidebar.append('
No annotations to display.
');
this.annotationSidebar.height(this.containerDim.y);
}
if (this.model.options.header == 'nav_bar') {
// Title-description panel
var title = this.model.node.getDisplayTitle();
if (!title || !title.length) title = '(No title)';
var desc = this.model.node.current.description;
if (!desc || !desc.length) desc = '(No description)';
var $desc = $('
'+title+'
'+desc+'
');
this.annotationSidebar.append($desc);
// Meta
var $meta = $('
');
var $table = $meta.find('table:first');
for (var j in this.model.node.current.properties) { // Load each meta field/value(s)
var field = j.substr( (j.lastIndexOf('/')+1) );
if (field.lastIndexOf('#')!=-1) field = field.substr( (field.lastIndexOf('#')+1) );
field = ucwords(field).replace('_', ' ');
if (field == 'PrimaryTopic') field = 'Source file';
var values = [];
for (var k = 0; k < this.model.node.current.properties[j].length; k++) {
var value = this.model.node.current.properties[j][k].value;
if (field.toLowerCase()=='url'&&value.indexOf('://')==-1) value = $('link#parent').attr('href')+value;
var chop_at = 40; // Magic number
var print_value = value;
if (print_value.indexOf(' ')==-1 && print_value.length>chop_at) print_value = wordwrap(print_value, chop_at, ' ', true); // Until I can figure out how to wordwrap in Safari/CSS
if (value.indexOf('://')!=-1) print_value = ''+print_value+'';
values.push(print_value);
}
$table.append('
');
this.annotationSidebar.append($meta);
// Float sidebar to left
this.annotationSidebar.addClass('annotationSidebarWithNavBar');
this.mediaContainer.addClass('mediaContainerWithNavBar');
}
this.updateSidebarDisplay();
}
/**
* Populates the footer element with content.
*/
jQuery.MediaElementView.prototype.populateFooter = function() {
// no footer if menu is showing
if ((this.model.options.header == 'nav_bar') || ('undefined'!=typeof(this.model.options.show_footer) && !this.model.options.show_footer)) {
this.footer.hide();
this.footer.remove();
// standard footer
} else if (!this.model.isChromeless) {
// show number of annotations if we're in vertical layout and not in the annotation editor
if (this.model.containerLayout == "vertical") {
if (-1==document.location.href.indexOf('annotation_editor')) {
var annoQuantity = $('
').appendTo(this.footer);
annoQuantity.css('width', '50%');
}
}
// if we're not in the annotation editor, then
if (-1==document.location.href.indexOf('annotation_editor')) {
// the View Page/View PDF button is initially hidden, this gives it the right text
// in case we do end up showing it
var extension = scalarapi.getFileExtension(this.model.path);
var viewString = 'View Page';
if (extension.toLowerCase() == 'pdf') {
viewString = 'View PDF';
}
var maximizeButton = $('
').appendTo(this.footer);
maximizeButton.addClass('mediaElementAlignRightDetails');
maximizeButton.siblings('.mediaElementAlignLeft').addClass('mediaElementAlignLeftDetails');
// opens original media file (for HTML pages and PDFs only)
maximizeButton.find('a#viewPageLink').data('me', this).on('click', function() {
var $this = $(this);
var base_dir = scalarapi.model.urlPrefix;
var path = $this.data('me').model.path;
var link = path;
if ($this.data('me').model.node.current.sourceLocation != null) link = $this.data('me').model.node.current.sourceLocation;
document.location.href = base_dir+'external?link='+encodeURIComponent(link)+'&prev='+encodeURIComponent(document.location.href);
});
// maximize link (now called 'Details' in the interface)
maximizeButton.find('a#maximizeLink').data('link', this.link).data('me', this).on('click', function() {
if (!$.isFunction($.fn.scalarmaximize)) return alert('Could not find maximize script.');
// pause all playing mediaelement instances
var view;
$('a').each(function() {
if ($(this).data('clone')) {
var clone = $(this).data('clone');
view = clone.data('mediaelement').view;
}
if (view) {
if (((view.model.mediaSource.contentType == 'video') || (view.model.mediaSource.contentType == 'audio')) && (view.isPlaying())) view.pause();
}
view = null;
if ($(this).data('mediaelement')) {
view = $(this).data('mediaelement').view;
}
if (view) {
if (((view.model.mediaSource.contentType == 'video') || (view.model.mediaSource.contentType == 'audio')) && (view.isPlaying())) view.pause();
}
});
var $link = $($(this).data('link'));
$link.scalarmaximize();
});
}
// dynamic blue annotation highlight
this.annotationDisplay = $('
This is the display
').appendTo(this.footer);
this.annotationDisplay.hide();
}
}
/**
* Parses the media type to determine what kind of player to create.
*/
jQuery.MediaElementView.prototype.parseMediaType = function() {
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] == null ) {
this.mediaObjectView = new $.UnsupportedObjectView(this.model, this);
} else {
var player;
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] != null ) {
player = this.model.mediaSource.browserSupport[scalarapi.scalarBrowser].player;
}
var isCulturallySensitive = false;
if (this.model.node.current.auxProperties['dcterms:accessRights'] != null) {
if (this.model.node.current.auxProperties['dcterms:accessRights'].indexOf('culturally-sensitive') != -1) {
isCulturallySensitive = true;
this.mediaObjectView = new $.CulturallySensitiveObjectView(this.model, this);
}
}
if (!isCulturallySensitive) {
switch (this.model.mediaSource.contentType) {
case '3D':
if (player == 'Threejs') {
this.mediaObjectView = new $.ThreejsObjectView(this.model, this);
} else if (this.model.mediaSource.name == 'Unity WebGL') {
this.mediaObjectView = new $.UnityWebGLObjectView(this.model, this);
}
break;
case '3D-GIS':
if (this.model.mediaSource.name == 'ArcGIS WebScene') {
this.mediaObjectView = new $.ArcGISObjectView(this.model, this);
}
break;
case 'image':
if (player == 'QuickTime') {
this.mediaObjectView = new $.QuickTimeObjectView(this.model, this);
} else {
this.mediaObjectView = new $.ImageObjectView(this.model, this);
}
break;
case 'tiledImage':
this.mediaObjectView = new $.DeepZoomImageObjectView(this.model, this);
break;
case 'manifest':
this.mediaObjectView = new $.MiradorObjectView(this.model, this);
break;
case 'audio':
if (this.model.mediaSource.name == 'SoundCloud') {
this.mediaObjectView = new $.SoundCloudAudioObjectView(this.model, this);
} else if (player == 'QuickTime') {
this.mediaObjectView = new $.QuickTimeObjectView(this.model, this);
} else if (player == 'native') {
this.mediaObjectView = new $.HTML5AudioObjectView(this.model, this);
}
break;
case 'video':
switch (player) {
case 'QuickTime':
if (this.model.mediaSource.name == 'QuickTimeStreaming') {
this.mediaObjectView = new $.StreamingQuickTimeObjectView(this.model, this);
} else {
this.mediaObjectView = new $.QuickTimeObjectView(this.model, this);
}
break;
case 'HLS':
if (this.model.node.current.auxProperties['dcterms:type'] == 'Sound') {
this.mediaObjectView = new $.HLSAudioObjectView(this.model, this);
} else {
this.mediaObjectView = new $.HLSVideoObjectView(this.model, this);
}
break;
case 'native':
if ($('.book-title').children('[data-semantic-annotation-tool]').length) {
this.mediaObjectView = new $.SemanticAnnotationToolObjectView(this.model, this);
} else {
if (this.model.node.current.auxProperties['dcterms:type'] == 'Sound') {
this.mediaObjectView = new $.HTML5AudioObjectView(this.model, this);
} else {
this.mediaObjectView = new $.HTML5VideoObjectView(this.model, this);
}
}
break;
case 'proprietary':
switch (this.model.mediaSource.name) {
case 'Vimeo':
this.mediaObjectView = new $.VimeoVideoObjectView(this.model, this);
break;
case 'YouTube':
this.mediaObjectView = new $.YouTubeVideoObjectView(this.model, this);
break;
}
break;
default:
this.mediaObjectView = new $.UnsupportedObjectView(this.model, this);
break;
}
break;
case 'map':
switch (this.model.mediaSource.name) {
case 'HyperCities':
if ((this.model.options.header == 'nav_bar') || (this.model.meta == scalarapi.stripAllExtensions(window.location.href)) || this.model.options.height) {
this.mediaObjectView = new $.HyperCitiesObjectView(this.model, this);
} else {
// replace HyperCities with maximize instructions if slot is too small
this.model.mediaSource = scalarapi.mediaSources.GIF;
this.mediaObjectView = new $.ImageObjectView(this.model, this);
this.model.path = this.model.mediaelement_dir +'hypercities_card.gif';
}
break;
case 'KML':
this.mediaObjectView = new $.GoogleMapsObjectView(this.model, this);
break;
default:
this.mediaObjectView = new $.UnsupportedObjectView(this.model, this);
break;
}
break;
case 'document':
switch (this.model.mediaSource.name) {
case 'HTML':
this.mediaObjectView = new $.HTMLObjectView(this.model, this);
break;
case 'PDF':
this.mediaObjectView = new $.PDFObjectView(this.model, this);
break;
case 'PlainText':
var queryVars = scalarapi.getQueryVars( this.model.path );
if ( queryVars.lang != null ) {
this.mediaObjectView = new $.SourceCodeObjectView(this.model, this);
} else {
this.mediaObjectView = new $.TextObjectView(this.model, this);
}
break;
case 'SourceCode':
this.mediaObjectView = new $.SourceCodeObjectView(this.model, this);
break;
case 'Prezi':
this.mediaObjectView = new $.PreziObjectView(this.model, this);
break;
}
break;
default:
this.mediaObjectView = new $.UnsupportedObjectView(this.model, this);
break;
}
}
}
}
/**
* Lays out the media object.
*/
jQuery.MediaElementView.prototype.layoutMediaObject = function() {
this.calculateMediaSize();
this.mediaObjectView.resize(this.resizedDim.x, this.resizedDim.y);
// magic number stuff to get layouts to look right
if ((this.model.containerLayout == "horizontal") && (this.model.options.header != 'nav_bar')) {
var fudgeAmount = 0;
var hMargin;
if (!this.annotationsVisible) {
switch (this.model.mediaSource.name) {
case "YouTube":
fudgeAmount = -2;
break;
}
this.model.element.width( this.resizedDim.x + fudgeAmount + (this.gutterSize * 2) );
} else {
switch (this.model.mediaSource.name) {
case "YouTube":
fudgeAmount = 2;
break;
case "MPEG-3":
case "WAV":
case "OGA":
case "AIFF":
fudgeAmount = 3;
break;
default:
fudgeAmount = 2;
break;
}
this.model.element.width(Math.max(this.resizedDim.x + fudgeAmount + parseInt(this.annotationSidebar.width()-1) + (this.gutterSize * 2), this.minContainerDim.x));
}
// update horizontal margins around media element
if (!this.model.isChromeless) {
hMargin = (this.initialContainerWidth - parseFloat(this.model.element.width())) * .5;
this.model.element.css('margin-left', hMargin);
}
} else {
if (this.model.isChromeless) {
this.model.element.width(this.resizedDim.x);
}
}
//console.log('element width: '+this.model.filename+' '+this.model.element.width());
}
/**
* Calculates the maximum allowed size of the media container.
*/
jQuery.MediaElementView.prototype.calculateContainerSize = function() {
var native_size = this.model.options.size == 'native';
var full_size = this.model.options.size == 'full';
switch (this.model.containerLayout) {
case "horizontal":
if ((native_size === true) || (full_size === true) || ( this.initialContainerWidth == 0 )) {
this.containerDim.x = parseInt(this.model.options.width);
} else {
this.containerDim.x = this.initialContainerWidth;
}
if (!this.model.isChromeless) {
this.containerDim.x--;
}
if (this.annotationsVisible) {
this.containerDim.x -= (parseInt(this.annotationSidebar.width()) + (this.gutterSize * 2));
}
if ((this.controllerOnly && (this.mediaContainer.closest('.slot').length == 0)) && (this.model.options.header != 'nav_bar')) {
this.containerDim.y = this.controllerHeight + (this.gutterSize * 2);
} else if (!this.model.isChromeless) {
this.containerDim.y = parseInt(this.model.options.height) - parseInt(this.header.height()) + 1;
} else {
this.containerDim.y = parseInt(this.model.options.height);
}
if (this.footer.attr('visible')) this.containerDim.y -= parseInt(this.footer.height());
this.minContainerDim.y = this.containerDim.y;
this.minContainerDim.x = Math.round(this.containerDim.y * .5);
if (this.containerDim.x < this.minContainerDim.x) this.containerDim.x = this.containerDim.y * 1.33;
break;
case "vertical":
this.containerDim.x = parseInt(this.model.options.width)
if (!this.model.isChromeless) this.containerDim.x -= 2;
if (this.controllerOnly) {
this.containerDim.y = Math.max(this.minContainerDim.y, this.controllerHeight + (this.gutterSize * 2));
} else if (this.model.isChromeless) {
if ( this.model.mediaSource.contentType != 'image' ) {
this.containerDim.y = window.innerHeight - 350 - parseInt(this.header.height()) - parseInt(this.footer.height());
} else {
this.containerDim.y = 1040 - parseInt(this.header.height()) - parseInt(this.footer.height());
}
} else {
this.containerDim.y = 375 - parseInt(this.header.height()) - parseInt(this.footer.height());
}
this.minContainerDim.x = this.containerDim.x;
this.minContainerDim.y = Math.round(this.containerDim.x * .33);
break;
}
if (this.model.mediaSource.contentType != 'image' && !full_size && !native_size) {
this.containerDim.y = Math.min( this.containerDim.y, window.innerHeight - 250 );
} else if ( scalarapi.getFileExtension( window.location.href ) == "annotation_editor" ) {
this.containerDim.y = Math.min( this.containerDim.y, window.innerHeight - 350 );
}
if (this.model.mediaSource.name == 'HyperCities') this.containerDim.x -= 4;
//console.log( 'container: ' + this.model.containerLayout+' '+this.initialContainerWidth+' '+this.containerDim.x+' '+this.containerDim.y+' '+this.footer.height()+' '+this.annotationSidebar.width());
}
/**
* Calculates the optimum display size for the media.
*/
jQuery.MediaElementView.prototype.calculateMediaSize = function() {
// if this is liquid media, always make it the maximum size
if (this.mediaObjectView.isLiquid) {
this.intrinsicDim = this.containerDim;
}
if (this.intrinsicDim.x == 0) {
// intrinsic dim unknown; assume 640x480
if (this.intrinsicDim.y == 0) {
this.intrinsicDim.x = 640;
this.intrinsicDim.y = 480;
// only y dimension known; make 1.33:1
} else {
this.intrinsicDim.x = this.intrinsicDim.y * 1.33;
this.intrinsicDim.y = this.intrinsicDim.y;
}
} else {
// only x dimension known; make 1.33:1
if (this.intrinsicDim.y == 0) {
this.intrinsicDim.x = this.intrinsicDim.x;
this.intrinsicDim.y = this.intrinsicDim.x / 1.33;
// both dimensions known; use as is
} else {
this.intrinsicDim.x = this.intrinsicDim.x;
this.intrinsicDim.y = this.intrinsicDim.y;
}
}
var mediaAR = this.intrinsicDim.x / this.intrinsicDim.y;
var containerAR = this.containerDim.x / this.containerDim.y;
//console.log( 'media: ' + this.intrinsicDim.x+' '+this.intrinsicDim.y+' '+mediaAR+' '+containerAR);
var native_size = this.model.options.size == 'native';
var tempDims = {
x: this.containerDim.x,
y: this.containerDim.y
};
// Set Maximum height for media types designated in typeLimits object;
var typeLimits = this.model.options.typeLimits;
var contentType = this.model.mediaSource.contentType;
var maxHeight;
if (typeof typeLimits == 'object') {
$.each(typeLimits, function(type, max) {
if (contentType == type) {
maxHeight = max;
} else if (type == 'default') {
if(typeof maxHeight == 'undefined') {
maxHeight = max;
}
}
});
if (typeof maxHeight != 'undefined') {
if (mediaAR > containerAR) {
// Must limit width in order to get the right height limit
tempDims.x = Math.min(maxHeight * mediaAR,tempDims.x);
} else {
tempDims.y = Math.min(maxHeight, tempDims.y);
}
}
}
if (mediaAR > containerAR) {
if(native_size && this.intrinsicDim.x < tempDims.x) {
tempDims.x = this.intrinsicDim.x;
this.native_full = false;
}
if (this.model.isChromeless) {
this.resizedDim.x = tempDims.x;
} else {
this.resizedDim.x = tempDims.x - (this.gutterSize * 2);
}
this.resizedDim.y = this.resizedDim.x / mediaAR + this.controllerOffset;
} else {
if(native_size && this.intrinsicDim.y < tempDims.y) {
tempDims.y = this.intrinsicDim.y;
}
if (this.model.isChromeless) {
this.resizedDim.y = tempDims.y;
} else {
this.resizedDim.y = tempDims.y - (this.gutterSize * 2);
}
if(this.mediaObjectView.isLiquid) {
this.resizedDim.x = tempDims.x;
} else {
this.resizedDim.x = (this.resizedDim.y - this.controllerOffset) * mediaAR;
}
}
//console.log( 'final container: ' + this.containerDim.x+' '+this.containerDim.y+' '+this.resizedDim.x+' '+this.resizedDim.y);
this.mediaScale = this.resizedDim.x / this.intrinsicDim.x;
this.updateMargins();
}
/**
* Calculates and updates the media object's margins
*/
jQuery.MediaElementView.prototype.updateMargins = function() {
switch (this.model.containerLayout) {
case "horizontal":
if (this.model.options.header == 'nav_bar') {
this.mediaMargins.horz = (this.containerDim.x - this.resizedDim.x) * .5 + this.gutterSize;
} else if (this.resizedDim.x < this.minContainerDim.x) {
this.mediaMargins.horz = (this.minContainerDim.x - this.resizedDim.x) * .5 + this.gutterSize;
} else {
this.mediaMargins.horz = this.gutterSize;
}
if ((this.controllerOnly && (this.mediaContainer.closest('.slot').length == 0)) && (this.model.options.header != 'nav_bar')) {
this.mediaMargins.vert = Math.max(0, (this.containerDim.y - this.controllerHeight) * .5);
} else {
this.mediaMargins.vert = Math.max(0, (this.containerDim.y - this.resizedDim.y) * .5);
}
break;
case "vertical":
this.mediaMargins.horz = (this.containerDim.x - this.resizedDim.x) * .5;
if (this.controllerOnly) {
this.mediaMargins.vert = (Math.max(this.minContainerDim.y, this.controllerHeight + (this.gutterSize * 2)) - this.controllerHeight) * .5;
} else if (this.resizedDim.y < this.minContainerDim.y) {
this.mediaMargins.vert = (this.minContainerDim.y - this.resizedDim.y) * .5;
} else {
this.mediaMargins.vert = this.gutterSize;
}
break;
}
this.mediaMargins.horz = Math.round(this.mediaMargins.horz);
var additive = 0;
if (this.model.options.header == 'nav_bar') {
additive = Math.max(0, this.gutterSize + Math.round(this.containerDim.x - (this.resizedDim.x+this.mediaMargins.horz+this.mediaMargins.horz))) - 3;
} else {
this.mediaMargins.horz--;
}
//console.log(this.model.path + ' margins '+this.mediaMargins.horz+' '+this.mediaMargins.vert);
if ( !this.model.isChromeless ) {
if ( this.model.mediaSource.name == "KML" ) { // exception for Google Maps, since padding just makes the map larger
this.mediaContainer.find('.mediaObject').css('margin-left', Math.floor(this.mediaMargins.horz));
} else {
this.mediaContainer.find('.mediaObject').css('padding-left', Math.floor(this.mediaMargins.horz));
this.mediaContainer.find('.mediaObject').css('padding-right', Math.floor(this.mediaMargins.horz + additive));
}
}
if ( !this.model.isChromeless || ( this.model.options.vcenter === true )) {
if ( this.model.mediaSource.name == "KML" ) { // exception for Google Maps, since padding just makes the map larger
this.mediaContainer.find('.mediaObject').css('margin-top', Math.floor(this.mediaMargins.vert));
} else {
this.mediaContainer.find('.mediaObject').css('padding-top', Math.floor(this.mediaMargins.vert));
this.mediaContainer.find('.mediaObject').css('padding-bottom', Math.floor(this.mediaMargins.vert));
}
}
if (!this.annotationsVisible) {
if (this.annotationDisplay) this.annotationDisplay.width( this.resizedDim.x + ( this.mediaMargins.horz * 2 ));
} else {
if (this.annotationDisplay) this.annotationDisplay.width(this.resizedDim.x + ( this.mediaMargins.horz * 2 ) + additive + 1 + this.annotationSidebar.width());
}
}
/**
* Toggles the annotation viewer open and closed.
*/
jQuery.MediaElementView.prototype.toggleAnnotations = function() {
this.annotationsVisible = !this.annotationsVisible;
this.updateSidebarDisplay();
}
/**
* Makes adjustments to show/hide the sidebar.
*/
jQuery.MediaElementView.prototype.updateSidebarDisplay = function() {
this.calculateContainerSize();
this.layoutMediaObject();
var player;
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] != null ) {
player = this.model.mediaSource.browserSupport[scalarapi.scalarBrowser].player;
}
if (this.annotationsVisible) {
if (this.model.options.header != 'nav_bar') {
$(this.annotationLink).find('a').text('Hide Annotations');
} else {
// if we're in the maximize view and this is an image with annotations, then the image annotations need to be reset
if ((this.model.mediaSource.contentType == 'image') && (player == 'native') && (this.mediaObjectView.annotations != null)) {
this.mediaObjectView.setupAnnotations(this.mediaObjectView.annotations);
}
}
if (this.model.containerLayout == "horizontal") {
this.annotationSidebar.css('display','block');
this.mediaContainer.css('float', 'right');
if (this.mediaContainer.closest('.slot').length == 0) {
if (this.controllerOnly || (this.model.options.header != 'nav_bar')) {
this.containerDim.y = parseInt(this.model.options.height) - parseInt(this.header.height()) + 1;
}
if (model.options.width == null) this.mediaContainer.height(this.containerDim.y);
}
}
} else {
if (this.model.options.header != 'nav_bar') {
$(this.annotationLink).find('a').text('Annotations');
} else {
// if we're in the maximize view and this is an image with annotations, then the image annotations need to be reset
if ((this.model.mediaSource.contentType == 'image') && (player == 'native') && (this.mediaObjectView.annotations != null)) {
this.mediaObjectView.setupAnnotations(this.mediaObjectView.annotations);
}
}
if (this.model.containerLayout == "horizontal") {
this.annotationSidebar.css('display','none');
if (this.mediaContainer.closest('.slot').length == 0) {
if (this.controllerOnly && (this.model.options.header != 'nav_bar')) {
this.containerDim.y = Math.max(this.minContainerDim.y, this.controllerHeight + (this.gutterSize * 2));
this.mediaContainer.removeAttr('height');
this.mediaContainer.css('height', '');
}
}
}
}
// update horizontal margins around media element
if (!this.model.isChromeless) {
var hMargin = Math.max(0, (this.initialContainerWidth - parseFloat(this.model.element.width())) * .5);
this.model.element.css('margin-left', hMargin);
}
this.doInstantUpdate();
}
/**
* Removes the loading message.
*/
jQuery.MediaElementView.prototype.removeLoadingMessage = function() {
if (this.mediaContainer.parent().parent().css('background-image') != 'none') {
this.mediaContainer.parent().parent().css('background-image', 'none');
$('body').trigger('mediaElementMediaLoaded', [$(this.model.link)]);
}
}
/**
* Begins playback of time-based media.
*/
jQuery.MediaElementView.prototype.play = function() {
if ((this.model.mediaSource.contentType == 'video') || (this.model.mediaSource.contentType == 'audio') || (this.model.mediaSource.contentType == 'document')) {
this.mediaObjectView.play();
this.overrideAutoSeek = false;
}
}
/**
* Starts the timer.
*
* @param {Object} event Object representing the play event.
* @param {Boolean} fireOnlyOnce If true, the timer will fire only once.
*/
this.startTimer = function(event, fireOnlyOnce) {
if (this.intervalId == null) {
if (fireOnlyOnce) {
setTimeout(function () {
handleTimer();
me.playingSeekAnnotation = false;
}, 0);
clearInterval(this.intervalId);
this.intervalId = null;
} else {
this.intervalId = setInterval(handleTimer, 100);
}
}
}
/**
* Handles per-frame timer activity.
*/
function handleTimer() {
var currentPosition = me.mediaObjectView.getCurrentTime();
// if we've reached the end of the annotation we were directed to seek to, then
// pause playback
if (me.playingSeekAnnotation && (me.model.mediaSource.contentType != 'document') && (currentPosition > me.model.seekAnnotation.properties.end)) {
me.playingSeekAnnotation = false;
me.pause();
}
if (me.annotations) {
var annotation;
var scrollTop = null;
var liveCount = 0;
var annoTitle;
var action = null;
var chip;
var startPosition;
var endPosition;
var newCurrentAnnotation;
var i;
var n = me.annotations.length;
// loop through all the annotations
for (i=0; i= (startPosition - .1)) && (currentPosition <= endPosition)) {
liveCount++;
// generate a display title for the annotation
if (me.model.mediaSource.contentType == 'document') {
annoTitle = 'Line '+currentPosition+' '+annotation.body.getDisplayTitle()+'';
} else {
annoTitle = scalarapi.decimalSecondsToHMMSS(me.getCurrentTime())+' '+annotation.body.getDisplayTitle()+'';
}
newCurrentAnnotation = annotation;
// if we just arrived at this annotation, then set up highlighting
if (!chip.hasClass('selectedAnnotation')) {
action = 'fadeIn';
chip.addClass('selectedAnnotation');
if (me.annotationsVisible) {
if (scrollTop == null) scrollTop = chip.position().top + me.annotationSidebar.scrollTop() - 30;
}
}
if (annotation == me.model.seekAnnotation) {
me.playingSeekAnnotation = true;
}
// if this annotation is inactive, turn off any highlighting
} else {
if (chip.hasClass('selectedAnnotation')) {
if (action == null) action = 'fadeOut';
chip.removeClass('selectedAnnotation');
}
}
// if the annotation sidebar is hidden,
} else {
// if this annotation is active, set up highlighting
if ((currentPosition >= (startPosition - .1)) && (currentPosition <= endPosition)) {
liveCount++;
// generate a display title for the annotation
if (me.model.mediaSource.contentType == 'document') {
annoTitle = 'Line '+currentPosition+' '+annotation.body.getDisplayTitle()+'';
} else {
annoTitle = scalarapi.decimalSecondsToHMMSS(me.getCurrentTime())+' '+annotation.body.getDisplayTitle()+'';
}
newCurrentAnnotation = annotation;
action = 'fadeIn';
if (annotation == me.model.seekAnnotation) {
me.playingSeekAnnotation = true;
}
// otherwise, turn off highlighting
} else {
if (action == null) action = 'fadeOut';
}
}
}
//console.log( "liveCount: " + liveCount );
if (me.annotationDisplay) {
// annotation highlight bar behavior
switch (action) {
case 'fadeIn':
me.annotationDisplay.fadeIn();
break;
case 'fadeOut':
if ((liveCount == 0) && (me.annotationDisplay.find('.annoSeekMessage').length == 0)) {
me.annotationDisplay.fadeOut();
if (me.model.mediaSource.contentType == 'document') {
$('body').trigger('hide_annotation', [this.currentAnnotation, me]); // Added by Craig for Live Annotations
}
}
break;
}
if (liveCount > 0) {
if (!me.annotationTimerRunning) {
me.annotationDisplay.html('
'+annoTitle+'
');
}
// don't show live annotations if we're in the annotation editor
if ((document.location.href.indexOf('.annotation_editor')==-1) && ('nav_bar' != me.model.options.header)) {
if ((me.model.mediaSource.name == 'Vimeo') || (me.model.mediaSource.name == 'SoundCloud')) {
// this prevents media generated from linked annotations from displaying
// their live annotation when cueing up on page load
if ((me.mediaObjectView.initialPauseDone) && (me.intervalId != null)) {
$('body').trigger('show_annotation', [newCurrentAnnotation, me]); // show live annotation
}
} else {
$('body').trigger('show_annotation', [newCurrentAnnotation, me]); // show live annotation
}
}
}
if ((scrollTop != null) && (me.annotationsVisible)) me.annotationSidebar.animate({scrollTop:scrollTop}, 500);
} else if (me.model.isChromeless) {
if (liveCount > 0) {
$('body').trigger('show_annotation', [newCurrentAnnotation, me]); // show live annotation
if (this.currentAnnotation != null) {
if (newCurrentAnnotation.body.url != this.currentAnnotation.body.url) {
$('body').trigger('hide_annotation', [this.currentAnnotation, me]);
}
}
} else {
$('body').trigger('hide_annotation', [this.currentAnnotation, me]);
}
}
}
if (liveCount == 0) {
this.currentAnnotation = null;
} else {
this.currentAnnotation = newCurrentAnnotation;
}
}
/**
* Ends the timer.
*/
jQuery.MediaElementView.prototype.endTimer = function() {
if (this.intervalId != null) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
/**
* Pauses playback of time-based media.
*/
jQuery.MediaElementView.prototype.pause = function() {
if ((this.model.mediaSource.contentType == 'audio') || (this.model.mediaSource.contentType == 'video') || (this.model.mediaSource.contentType == 'document')) {
this.mediaObjectView.pause();
}
}
/**
* Seeks to a given time position within a piece of time-based media.
*
* @param {Object} annotation The annotation to seek to.
*/
this.seek = function(annotation) {
switch (this.model.mediaSource.contentType) {
case 'audio':
case 'video':
case 'document':
this.model.seekAnnotation = annotation;
this.playingSeekAnnotation = true;
if (this.annotationDisplay) {
this.annotationDisplay.html('
Seeking to '+annotation.startString+'…
');
this.annotationDisplay.fadeIn();
}
if (!this.annotationTimerRunning) {
var delay = 3000;
// if the user clicked on an annotation chip, then they're expecting the clip to play asap,
// so schedule our first check sooner than normal
if (this.cachedPlayCommand) {
delay = 1000;
}
this.annotationTimer = setTimeout(checkSeekSuccessful, delay);
this.annotationTimerRunning = true;
}
this.overrideAutoSeek = true;
this.mediaObjectView.seek(annotation.properties.start, annotation.properties.end);
this.lastSeekTime = annotation.properties.start;
handleTimer();
break;
case '3D':
case '3D-GIS':
this.mediaObjectView.seek(annotation.properties);
if (me.model.isChromeless || ('nav_bar' != me.model.options.header)) {
$('body').trigger('show_annotation', [annotation, me]);
}
break;
case 'image':
this.showSpatialAnnotation(annotation);
break;
}
}
/**
* Shows the specified spatial annotation.
*
* @param {Object} annotation The spatial annotation to be shown.
*/
this.showSpatialAnnotation = function(annotation) {
if(typeof imagesWithAnnotations[me.model.filename + '-' + this.mediaObjectView.model.id + '-' + annotation.id] != 'undefined'){
anno.highlightAnnotation( imagesWithAnnotations[me.model.filename + '-' + this.mediaObjectView.model.id + '-' + annotation.id] );
}else{
anno.highlightAnnotation( annotation.data );
}
if (me.model.isChromeless || ('nav_bar' != me.model.options.header)) {
$('body').trigger('show_annotation', [annotation, me]);
}
}
/**
* Checks to see if the last attempted seek has succeeded; if not, tries to seek there again.
* @return Returns true if the seek was successful.
*/
function checkSeekSuccessful() {
//console.log('check to see if seek successful: '+me.lastSeekTime+' '+me.mediaObjectView.getCurrentTime());
var result = false;
if ( me.annotationDisplay || me.model.isChromeless ) {
// we're not yet in range; seek must have failed
if ((me.lastSeekTime - me.mediaObjectView.getCurrentTime()) > 5) {
me.mediaObjectView.seek(me.lastSeekTime);
me.annotationTimer = setTimeout(checkSeekSuccessful, 3000);
me.seekAttemptCount++;
//console.log('not yet in range, making attempt #'+me.seekAttemptCount);
// we are in range; stop seeking and hide the 'attempting' message if visible
} else {
if ( !me.model.isChromeless ) {
if ((me.annotationDisplay.text().indexOf('Seeking') != -1) && !me.cachedPlayCommand && !me.mediaObjectView.isPlaying() ) {
me.annotationDisplay.fadeOut();
}
}
me.annotationTimerRunning = false;
//console.log( "seek successful" );
// if we know the user intends for the clip to start playing, do so
if ( me.cachedPlayCommand || me.model.options.autoplay ) {
me.play();
}
result = true;
}
}
return result;
}
/**
* Returns true if time-based media is currently playing.
* @return Returns true if the media is playing.
*/
jQuery.MediaElementView.prototype.isPlaying = function() {
return this.mediaObjectView.isPlaying();
}
/**
* For time-based media, returns the current playback time.
* @return Returns the current playback time for temporal media in seconds.
*/
this.getCurrentTime = function() {
if ((this.model.mediaSource.contentType == 'audio') || (this.model.mediaSource.contentType == 'video') || (this.model.mediaSource.contentType == 'document')) {
return this.mediaObjectView.getCurrentTime();
} else {
return null;
}
}
this.getPosition3D = function() {
if (this.model.mediaSource.contentType == '3D' || this.model.mediaSource.contentType == '3D-GIS') {
return this.mediaObjectView.getPosition3D();
} else {
return null;
}
}
this.handleAnnotationsUpdated = function() {
if (this.model.mediaSource.contentType == '3D' || this.model.mediaSource.contentType == '3D-GIS') {
return this.mediaObjectView.handleAnnotationsUpdated();
} else {
return null;
}
}
jQuery.MediaElementView.prototype.updateAnnotations = function( annotations ) {
this.annotations = annotations;
}
/**
* Parses annotation data and adds it to the view.
*/
jQuery.MediaElementView.prototype.parseAnnotations = function() {
this.annotations = [];
var annotation;
var annoType;
var data;
var index;
var coordinate;
this.annotations = this.model.node.getRelations('annotation', 'incoming');
var annotationWhiteList = $(this.link).data('annotations');
if($(this.link).is('[data-annotations]') || annotationWhiteList){
if(!annotationWhiteList){
annotationWhiteList = [];
} else if (typeof annotationWhiteList === "string") {
annotationWhiteList = annotationWhiteList.split(",");
} else if (typeof annotationWhiteList === "number") {
annotationWhiteList = [annotationWhiteList.toString()];
}
var temp_annotations = [];
for(var i = 0; i < this.annotations.length; i++){
if(annotationWhiteList.indexOf(this.annotations[i].body.slug)!=-1){
temp_annotations.push(this.annotations[i]);
}
}
this.annotations = temp_annotations;
}
/*if ( this.annotationSidebar ) {
if (this.model.options.header != 'nav_bar') {
this.annotationSidebar.empty();
}
}*/
// if this instance was passed an initial seek value, then keep track
// of the annotation it was directed to seek to
var i;
var n = this.annotations.length;
var annotation;
for (i=0; i 0) {
if (this.header != null) {
this.header.find('p.mediaElementAlignLeft').css('width', '79%'); // make room for the annotation link
}
if (this.annotationSidebar) {
this.annotationSidebar.find('div.annotationChip:first').remove();
}
}
var annotationChip;
var extents;
var stripeType;
var i;
var n = this.annotations.length;
for (i=0; i');
switch (this.model.mediaSource.contentType) {
case 'audio':
case 'video':
extents = $('');
var link;
link = $(''+annotation.startString+annotation.separator+annotation.endString+'').appendTo(extents);
// Seek on the view
annotationChip.data('annotation', annotation);
annotationChip.data('me', this.controller.view);
annotationChip.on('click', function() {
var $me = $(this).data('me');
// don't cache the play command for YouTube videos since they play automatically on seeking anyway
if ( local_me.model.mediaSource.name != "YouTube" ) {
$me.cachedPlayCommand = true;
}
$me.seek($(this).data('annotation'));
setTimeout(function() {
$me.play();
},250);
});
annotationChip.append('
'+annotation.body.getDisplayTitle()+'
');
annotationChip.append(extents);
break;
case 'image':
annotationChip.append('
');
annotationChip.data('annotation', annotation);
annotationChip.data('me', this.controller.view);
annotationChip.on('click', function() {
$(this).data('me').cachedPlayCommand = true;
$(this).data('me').seek($(this).data('annotation'));
// don't show live annotations if we're in the annotation editor or the maximize view
if (document.location.href.indexOf('.annotation_editor') && ($(this).data('me').model.options.header != 'nav_bar')) {
$('body').trigger('show_annotation', [$(this).data('annotation'), $(this).data('me')]);
}
});
break;
case 'document':
extents = $('');
var link = $(''+annotation.startString+annotation.separator+annotation.endString+'').appendTo(extents);
// Seek on the view
annotationChip.data('annotation', annotation);
annotationChip.data('me', this.controller.view);
annotationChip.on('click', function(e) {
$(this).data('me').model.seekAnnotation = null;
$(this).data('me').seek($(this).data('annotation'));
$(this).data('me').play();
});
annotationChip.append(extents);
annotationChip.append('
'+annotation.body.getDisplayTitle()+'
');
break;
}
var annoText = $('');
// if the annotation has a description, then show it
if (annotation.body.current.description != null) {
annoText.append(''+annotation.body.current.description+'');
}
// if the annotation has content, then show it
if (annotation.body.current.content && annotation.body.current.content.length > 0) {
if (annotation.body.current.description != null) annoText.append(' ');
var the_content = annotation.body.current.content;
var num_words = 35; // Magic number
if ('function' == typeof(window['create_excerpt'])) the_content = create_excerpt(the_content,num_words);
annoText.append(the_content);
}
// Permalink
annoText.append(' Permalink »');
annotationChip.append(annoText);
if (this.annotationSidebar) {
this.annotationSidebar.append(annotationChip);
}
}
break;
case "vertical":
if (this.annotations.length > 0) {
var annoText;
if (this.annotations.length == 1) {
annoText = this.annotations.length+' annotation';
} else {
annoText = this.annotations.length+' annotations';
}
if (this.footer) {
this.footer.find('p.mediaElementAlignLeft').text(annoText);
}
}
break;
}
var playerIsNative = false;
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] != null ) {
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser].player == 'native' ) {
playerIsNative = true;
}
}
// show image annotations if there are any
if ((this.model.mediaSource.contentType == 'image') && playerIsNative && (this.annotations.length > 0)) {
this.mediaObjectView.setupAnnotations(this.annotations);
}
// if we were passed an annotation to seek to, wait a while before attempting to seek
// (if YouTube, then don't try to seek if we're not on Mobile Safari, since we'll set player
// params to seek instead)
if (this.model.seekAnnotation != null) {
if (this.annotationDisplay && (this.model.mediaSource.contentType != 'image')) {
this.annotationDisplay.html('
Seeking to '+this.model.seekAnnotation.startString+'…
');
this.annotationDisplay.fadeIn();
}
if ( ( this.model.mediaSource.contentType != 'image' ) && ( this.model.mediaSource.contentType != 'document' ) ) {
setTimeout( $.proxy(function(me){this.doAutoSeek(this);},this), 6000 );
} else if(!this.model.mediaSource.contentType == 'document' || !(this.model.mediaSource.name == 'SourceCode' || scalarapi.getQueryVars( this.model.path ).lang != null)){
setTimeout( $.proxy(function(me){this.doAutoSeek(this);},this), 3000 );
}
}
if ('nav_bar' == this.model.options.header) {
if (this.annotationSidebar) {
var $chips = this.annotationSidebar.find('.annotationChip').hide();
}
}
}
/**
* Seeks to the annotation that was passed into the mediaelement instance.
*/
this.doAutoSeek = function(me) {
me.seek(this.model.seekAnnotation);
this.sendMessage(this.annotationHasMessage(this.model.seekAnnotation));
// YouTube videos will play immediately on seeking unless we do this
if ( this.model.mediaSource.name == 'YouTube' ) {
me.pause();
}
}
this.sendMessage = function(message) {
if (this.mediaObjectView.sendMessage) {
this.mediaObjectView.sendMessage(message);
}
}
this.annotationHasMessage = function(annotation) {
let result = false;
if (annotation.body.current.properties['http://purl.org/dc/terms/abstract']) {
result = annotation.body.current.properties['http://purl.org/dc/terms/abstract'][0].value;
}
return result;
}
this.doInstantUpdate = function() {
me.startTimer(null, true);
}
}
/**
* View for the image player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.ImageObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.hasLoaded = false; // has the image loaded?
this.annotations = null; // annotations for the image
this.annotatedImage = null; // annotated image object
/**
* Creates the image media object.
*/
jQuery.ImageObjectView.prototype.createObject = function() {
this.wrapper = $('');
this.image = new Image();
if (this.model.node.current.description != undefined) {
this.image.alt = this.model.node.current.description.replace(/([^"\\]*(?:\\.[^"\\]*)*)"/g, '$1\\"');
} else {
this.image.alt = '';
}
$(this.image).appendTo(this.wrapper);
$(this.image).css('display', 'none');
$(this.wrapper).appendTo(this.parentView.mediaContainer);
var url = this.model.path;
var queryVars = scalarapi.getQueryVars( document.location.href );
if ( queryVars.t != null ) {
url += '?t=' + new Date().getTime();
}
// special case some URL elements
if (-1!=url.indexOf('http://cubantheater.org')) url = url.replace('http://cubantheater.org','http://ctda.library.miami.edu');
$(this.image).attr({
'src': url,
'data-original': url + '-' + this.model.id // needed to support annotorious if the same image appears on the page more than once
});
$(this.image).on('load', function() {
if ('undefined' != typeof(me.model.node.current.properties["http://ns.google.com/photos/1.0/panorama/stitchingsoftware"])) { // Test for 360 image
me.parentView.mediaContainer.empty();
me.parentView.mediaObjectView = new $.I360ObjectView(model, parentView);
me.parentView.mediaObjectView.annotations = me.annotations;
me.parentView.mediaObjectView.createObject();
return;
}
me.doImageSetup(this);
});
}
jQuery.ImageObjectView.prototype.doImageSetup = function(image) {
this.hasLoaded = true;
this.parentView.intrinsicDim.x = image.width;
this.parentView.intrinsicDim.y = image.height;
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
// Make visible
$(image).hide().fadeIn();
if (this.annotations != null) {
this.setupAnnotations(this.annotations);
}
}
/**
* Sets up the image's annotations
*
* @param {Array} annotations The annotations to be added to the image.
*/
jQuery.ImageObjectView.prototype.setupAnnotations = function(annotations) {
if (!this.hasLoaded) {
this.annotations = annotations;
} else {
var i, annotation, x, y, width, height,
notes = [],
n = annotations.length;
// first time setup
if ( anno.getAnnotations( this.image.src + '-' + this.model.id ).length == 0 ) {
// if the image is in a carousel, temporarily show it so we can attach annotations to ti
var isAlreadyActive = $(this.image).closest('.item').hasClass('active');
if (!isAlreadyActive) {
$(this.image).closest('.item').addClass('active');
}
anno.makeAnnotatable( this.image );
anno.hideSelectionWidget( this.image.src + '-' + this.model.id );
anno.setProperties( { hi_stroke: "#3acad9" } );
// if the image is in a carousel, return it to its original state
if (!isAlreadyActive) {
$(this.image).closest('.item').removeClass('active');
}
// reload
} else {
anno.removeAll( this.image.src );
}
var editable = ( document.location.href.indexOf( '.annotation_editor' ) != -1 );
for (i=0; i' + annotation.body.getDisplayTitle() + " " + (( annotation.body.current.content != null ) ? annotation.body.current.content+"" : "" ),
editable: editable,
shapes: [{
type: "rect",
units: "pixel",
geometry: { x: x, y: y, width: width, height: height }
}],
isMedia : annotationIsMediaType,
element : this.model.element[0]
};
$el.attr('id',this.model.filename + '-' + this.model.id);
var currentData = {};
if($el.data('annotations') != undefined){
var currentData = $el.data('annotations');
}
annotation.data = annodata;
currentData[annotation.id] = annodata;
imagesWithAnnotations[this.model.filename + '-' + this.model.id + '-' + annotation.id] = annodata;
$el.data('annotations',currentData);
anno.addAnnotation( annotation.data );
}
$(".annotorious-popup").on('click', function(e) {
e.stopPropagation();
return true;
}).css('z-index',999);
$(this.wrapper).find( ".annotorious-popup-text" ).addClass( "caption_font" );
}
}
// These functions are basically irrelevant for images
jQuery.ImageObjectView.prototype.play = function() { }
jQuery.ImageObjectView.prototype.pause = function() { }
jQuery.ImageObjectView.prototype.seek = function(time) { }
jQuery.ImageObjectView.prototype.getCurrentTime = function() { return null; }
jQuery.ImageObjectView.prototype.isPlaying = function() { return true; } // we depend on images returning true here to handle annotation cueing
/**
* Resizes the image to the specified dimensions.
*
* @param {Number} width The new width of the image.
* @param {Number} height The new height of the image.
*/
jQuery.ImageObjectView.prototype.resize = function(width, height) {
if ((this.model.containerLayout == 'horizontal') && (this.model.options.width != null)) {
/*if ((width < this.model.options.width) && (width > (1040 - 144))) {
var scaleFactor = (1040 - 144) / width;
width *= scaleFactor;
height *= scaleFactor;
}*/
}
$(this.image).parent().width(width+'px');
$(this.image).parent().height(height+'px');
if (this.hasLoaded) {
$(this.image).closest( '.mediaObject' ).width( '' );
$(this.image).closest( '.mediaObject' ).height( '' );
$(this.image).width(width+'px');
$(this.image).height(height+'px');
}
}
} // !image
/**
* View for 360 images, using Google's vrview.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.I360ObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.hasLoaded = false; // has the image loaded?
this.annotations = null; // vrview hotspots
this.annotatedImage = null; // hotspotted image object
/**
* Creates the 360 media object.
*/
jQuery.I360ObjectView.prototype.createObject = function() {
this.hasLoaded = true;
//this.parentView.intrinsicDim.x = 6000;
//this.parentView.intrinsicDim.y = 3000;
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
if ('undefined' == typeof(vrviews)) vrviews = 0; // Global
var my_vrview = parseInt(vrviews);
vrviews++;
this.wrapper = $('');
$(this.wrapper).appendTo(this.parentView.mediaContainer);
var url = this.model.path;
var path = $('link#approot').attr('href')+'views/widgets/vrview/build/';
var obj = {
image: url,
script_path: path
};
$.getScript( path+'vrview.js' , function() {
var vrView = new VRView.Player('#vrview_'+my_vrview, obj);
$('#vrview_'+my_vrview).find('iframe').css('width','100%').css('height','100%');
vrView.on('ready', function(){
if (me.annotations != null) {
me.setupAnnotations(vrView, me.annotations);
}
});
});
}
/**
* Sets up the image's annotations
*
* @param {Array} annotations The annotations to be added to the image.
*/
jQuery.I360ObjectView.prototype.setupAnnotations = function(vrView, annotations) {
for (i=0; i 500) height = width * 0.75; // Artificially restrict the height
if (height > 400) height = 400; // Artificially cap the height
$(me.parentView.mediaContainer).width(width+'px');
$(me.parentView.mediaContainer).height(height+'px');
}
jQuery.I360ObjectView.prototype.play = function() { }
jQuery.I360ObjectView.prototype.pause = function() { }
jQuery.I360ObjectView.prototype.seek = function(time) { }
jQuery.I360ObjectView.prototype.getCurrentTime = function() { return null; }
jQuery.I360ObjectView.prototype.isPlaying = function() { return true; }
} // !360
/**
* View for the QuickTime player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.QuickTimeObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the video media object.
*/
jQuery.QuickTimeObjectView.prototype.createObject = function() {
if ('undefined'==typeof(QT_GenerateOBJECTText_XHTML)) return;
var showController = 'True';
if (this.model.mediaSource.contentType == 'image') {
showController = 'False';
}
var movie = QT_GenerateOBJECTText_XHTML(
this.model.path, '100%', '100%', '',
"obj#id", this.model.filename+'_object'+this.model.id,
"emb#id", this.model.filename+'_embed'+this.model.id,
'postdomevents', 'True',
"emb#NAME", this.model.filename+'_object'+this.model.id,
'EnableJavaScript', 'True',
'AUTOPLAY', this.model.options.autoplay,
'CONTROLLER', showController,
'SCALE', 'Aspect'
);
var $theHTML = $('
'+movie+'
');
this.parentView.mediaContainer.html($theHTML);
if (this.model.mediaSource.contentType == 'audio') {
this.parentView.controllerOffset = 23;
} else {
this.parentView.controllerOffset = 15;
}
this.metadataFunc = function() {
if (!me.hasReceivedMetadata) {
me.hasReceivedMetadata = true;
var video = document.getElementById(me.model.filename+'_embed'+me.model.id);
var dimensions = video.GetRectangle();
var temp = dimensions.split(',');
var h = Number(temp[3]);
var w = Number(temp[2]);
me.parentView.intrinsicDim.x = Number(temp[2]);
me.parentView.intrinsicDim.y = Number(temp[3]);
me.parentView.layoutMediaObject();
me.parentView.removeLoadingMessage();
}
}
var video = $theHTML.children()[0];
if (document.addEventListener) {
video.addEventListener('qt_play', me.parentView.startTimer, false);
video.addEventListener('qt_pause', me.parentView.endTimer, false);
video.addEventListener('qt_ended', me.parentView.endTimer, false);
video.addEventListener('qt_loadedmetadata', this.metadataFunc, false);
} else {
video.attachEvent('onqt_play', me.parentView.startTimer);
video.attachEvent('onqt_pause', me.parentView.endTimer);
video.attachEvent('onqt_ended', me.parentView.endTimer);
video.attachEvent('onqt_loadedmetadata', this.metadataFunc);
}
return;
}
/**
* Starts playback of the QuickTime movie.
*/
jQuery.QuickTimeObjectView.prototype.play = function() {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
video.SetTime(this.model.seekAnnotation.properties.start * video.GetTimeScale());
}
if (video.GetRate() == 0) {
video.Play();
}
}
}
/**
* Pauses playback of the QuickTime movie.
*/
jQuery.QuickTimeObjectView.prototype.pause = function() {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) video.Stop();
}
/**
* Seeks to the specified location in the QuickTime movie.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.QuickTimeObjectView.prototype.seek = function(time) {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) video.SetTime(time * video.GetTimeScale());
}
/**
* Returns the current playback position of the QuickTime movie.
* @return The current playback position in seconds.
*/
jQuery.QuickTimeObjectView.prototype.getCurrentTime = function() {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) {
return (video.GetTime() / video.GetTimeScale());
} else {
return 0;
}
}
/**
* Resizes the QuickTime movie to the specified dimensions.
*
* @param {Number} width The new width of the video.
* @param {Number} height The new height of the video.
*/
jQuery.QuickTimeObjectView.prototype.resize = function(width, height) {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
var parent = $(video).parent().parent();
parent.width(Math.round(width));
parent.height(Math.round(height));
}
/**
* Returns true if the QuickTime video is currently playing.
* @return Returns true if the video is playing.
*/
jQuery.QuickTimeObjectView.prototype.isPlaying = function() {
result = false;
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) {
return (video.GetRate() != 0);
}
return result;
}
}
/**
* View for the streaming QuickTime player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.StreamingQuickTimeObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the video media object.
*/
jQuery.StreamingQuickTimeObjectView.prototype.createObject = function() {
if ('undefined'==typeof(QT_GenerateOBJECTText_XHTML)) return;
var segments = this.model.base_dir.split('/');
segments.pop();
segments.pop();
appDir = segments.join('/');
var movie = QT_GenerateOBJECTText_XHTML(
this.model.path, '100%', '100%', '',
"obj#id", this.model.filename+'_object'+this.model.id,
"emb#id", this.model.filename+'_embed'+this.model.id,
'postdomevents', 'True',
"emb#NAME", this.model.filename+'_object'+this.model.id,
'EnableJavaScript', 'True',
'SCALE', 'Aspect',
'AUTOPLAY', this.model.options.autoplay,
'TYPE', 'video/quicktime',
'CACHE', 'False'
);
var $theHTML = $('
'+movie+'
');
this.parentView.mediaContainer.html($theHTML);
this.metadataFunc = function() {
if (!me.hasReceivedMetadata) {
me.hasReceivedMetadata = true;
me.resizeMovie();
}
}
var video = $theHTML.children()[0];
if (document.addEventListener) {
video.addEventListener('qt_play', me.parentView.startTimer, false);
video.addEventListener('qt_pause', me.parentView.endTimer, false);
video.addEventListener('qt_ended', me.parentView.endTimer, false);
video.addEventListener('qt_loadedmetadata', this.metadataFunc, false);
} else {
video.attachEvent('onqt_play', me.parentView.startTimer);
video.attachEvent('onqt_pause', me.parentView.endTimer);
video.attachEvent('onqt_ended', me.parentView.endTimer);
video.attachEvent('onqt_loadedmetadata', this.metadataFunc);
}
return;
}
/**
* Lays out the player to accommodate the movie's native dimensions.
*/
jQuery.StreamingQuickTimeObjectView.prototype.resizeMovie = function() {
var video = document.getElementById(me.model.filename+'_embed'+me.model.id);
w = video.videoWidth;
h = video.videoHeight;
me.parentView.intrinsicDim.x = w + 6;
me.parentView.intrinsicDim.y = h;
me.parentView.layoutMediaObject();
me.parentView.removeLoadingMessage();
}
/**
* Starts playback of the QuickTime movie.
*/
jQuery.StreamingQuickTimeObjectView.prototype.play = function() {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
video.SetTime(this.model.seekAnnotation.properties.start * video.GetTimeScale());
}
if (video.GetRate() == 0) {
video.Play();
}
}
}
/**
* Pauses playback of the QuickTime movie.
*/
jQuery.StreamingQuickTimeObjectView.prototype.pause = function() {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) video.Stop();
}
/**
* Seeks to the specified location in the QuickTime movie.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.StreamingQuickTimeObjectView.prototype.seek = function(time) {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) video.SetTime(time * video.GetTimeScale());
}
/**
* Returns the current playback position of the QuickTime movie.
* @return The current playback position in seconds.
*/
jQuery.StreamingQuickTimeObjectView.prototype.getCurrentTime = function() {
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) {
return (video.GetTime() / video.GetTimeScale());
} else {
return 0;
}
}
/**
* Resizes the QuickTime movie to the specified dimensions.
*
* @param {Number} width The new width of the video.
* @param {Number} height The new height of the video.
*/
jQuery.StreamingQuickTimeObjectView.prototype.resize = function(width, height) {
width = Math.round(width);
height = Math.round(height);
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video) video.SetRectangle('0.0,0.0,'+width+'.0,'+height+'.0');
var parent = $(video).parent().parent();
parent.width(Math.round(width));
parent.height(Math.round(height));
}
/**
* Returns true if the QuickTime video is currently playing.
* @return Returns true if the video is playing.
*/
jQuery.StreamingQuickTimeObjectView.prototype.isPlaying = function() {
result = false;
var video = document.getElementById(this.model.filename+'_embed'+this.model.id);
if (video && this.hasReceivedMetadata) {
return (video.GetRate() != 0);
}
return result;
}
}
/**
* View for the HTML5 video player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.HTML5VideoObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the video media object.
*/
jQuery.HTML5VideoObjectView.prototype.createObject = function() {
var mimeType;
switch (this.model.mediaSource.name) {
case "QuickTime":
mimeType = "video/quicktime"
break;
case "MPEG-4":
case "M4V":
mimeType = "video/mp4";
break;
case "OGG":
mimeType = "video/ogg";
break;
case "3GPP":
mimeType = "video/3gpp";
break;
case "CriticalCommons-LegacyVideo":
var temp = this.model.path.split('//');
if (temp.length == 3) {
var temp2 = temp[2].split('-');
temp2.pop();
var chunk = temp2.join('-');
chunk = chunk.replace(/%20/g, '-');
temp = chunk.split('/');
var slug = temp[temp.length - 1];
var url = 'http://videos.criticalcommons.org/transcoded/http/ccserver.usc.edu/8080/cc/Members/' + chunk + '.mp4/mp4-high/' + slug.toLowerCase() + '-mp4.mp4';
this.model.path = url;
}
mimeType = this.updateCriticalCommonsURLForBrowser();
break;
case "CriticalCommons-Video":
mimeType = this.updateCriticalCommonsURLForBrowser();
break;
case "WebM":
mimeType = "video/webm";
break;
case "HLS":
mimeType = "application/x-mpegURL";
var hls = true;
break;
}
obj = $('').appendTo(this.parentView.mediaContainer);
if ( this.model.options.autoplay && ( this.model.seek == null ) ) {
obj.find( 'video' ).attr( 'autoplay', 'true' );
}
// apply the poster image only if the thumbnail loads successfully
var thumbnailURL;
if (this.model.node.thumbnail) {
thumbnailURL = this.model.node.getAbsoluteThumbnailURL();
// legacy thumbnails are sometimes stored with the version
} else if (this.model.node.current.thumbnail) {
thumbnailURL = this.model.node.current.thumbnail;
}
if (thumbnailURL != undefined) {
// finding the right thumbnail Critical Commons thumbnail image
if ((this.model.mediaSource.name == 'CriticalCommons-LegacyVideo') || (this.model.mediaSource.name == 'CriticalCommons-Video')) {
thumbnailURL = thumbnailURL.replace('thumbnailImage_thumb', 'thumbnailImage');
thumbnailURL = thumbnailURL.replace('thumbnailImage_small', 'thumbnailImage');
}
this.image = new Image();
// setup actions to be taken on image load
$(this.image).on('load', function() {
var $this = $(this);
me.video.attr('poster', $this.attr('src'));
}).attr('src', thumbnailURL);
}
this.video = obj.find('video#'+this.model.filename+'_'+this.model.id);
this.parentView.controllerOffset = 22;
var metadataFunc = function() {
// Until there's a way to find out from the MP4 if it's an equirectangular video...
if ('undefined' != typeof(me.model.node.current.properties["http://ns.google.com/photos/1.0/panorama/stitchingsoftware"])) {
me.parentView.mediaContainer.empty();
me.parentView.mediaObjectView = new $.V360ObjectView(model, parentView);
me.parentView.mediaObjectView.createObject();
return;
}
if (me.parentView.intrinsicDim.x != me.video[0].videoWidth || me.parentView.intrinsicDim.y != me.video[0].videoHeight) {
me.parentView.intrinsicDim.x = me.video[0].videoWidth;
me.parentView.intrinsicDim.y = me.video[0].videoHeight;
me.parentView.controllerOffset = 0;
} console.log('metadatafunc');
if (hls) {
me.video[0].removeEventListener('progress', metadataFunc, false);
}
me.parentView.layoutMediaObject();
me.parentView.removeLoadingMessage();
}
this.parentView.layoutMediaObject();
if (document.addEventListener) {
// When streaming HLS in Safari's native player, the data must be loaded before the video's dimensions can be gathered. Mobile Safari requires this be progress.
if (hls) {
this.video[0].addEventListener('progress', metadataFunc, false);
} else {
this.video[0].addEventListener('loadedmetadata', metadataFunc, false);
}
this.video[0].addEventListener('play', me.parentView.startTimer, false);
this.video[0].addEventListener('pause', me.parentView.endTimer, false);
this.video[0].addEventListener('ended', me.parentView.endTimer, false);
} else {
if (hls) {
this.video[0].attachEvent('onprogress', metadataFunc);
} else {
this.video[0].attachEvent('onloadedmetadata', metadataFunc);
}
this.video[0].attachEvent('play', me.parentView.startTimer);
this.video[0].attachEvent('pause', me.parentView.endTimer);
this.video[0].attachEvent('ended', me.parentView.endTimer);
}
return;
}
jQuery.HTML5VideoObjectView.prototype.updateCriticalCommonsURLForBrowser = function() {
var ext, format, mimeType;
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] != null ) {
format = this.model.mediaSource.browserSupport[scalarapi.scalarBrowser].format;
}
if (format == 'MPEG-4') {
ext = 'mp4';
mimeType = "video/mp4";
} else {
ext = 'webm';
mimeType = "video/webm";
this.model.path = this.model.path.replace('mp4-high','webm-high');
this.model.path = this.model.path.replace('mp4-low','webm-low');
}
var path_segments = this.model.path.split('.');
if (path_segments.length > 1) {
path_segments[path_segments.length-1] = ext;
this.model.path = path_segments.join('.');
}
return mimeType;
}
/**
* Starts playback of the video.
*/
jQuery.HTML5VideoObjectView.prototype.play = function() {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
this.video[0].currentTime = this.model.seekAnnotation.properties.start;
}
if (this.video[0].paused) {
this.video[0].play();
}
}
/**
* Pauses playback of the video.
*/
jQuery.HTML5VideoObjectView.prototype.pause = function() {
if (this.video[0]) {
this.video[0].pause();
}
}
/**
* Seeks to the specified location in the video.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.HTML5VideoObjectView.prototype.seek = function(time) {
if (this.video[0]) {
this.video[0].currentTime = time;
}
}
/**
* Returns the current playback position of the video.
* @return The current playback position in seconds.
*/
jQuery.HTML5VideoObjectView.prototype.getCurrentTime = function() {
if (this.video[0]) {
return this.video[0].currentTime;
}
}
/**
* Resizes the video to the specified dimensions.
*
* @param {Number} width The new width of the video.
* @param {Number} height The new height of the video.
*/
jQuery.HTML5VideoObjectView.prototype.resize = function(width, height) {
if (this.video) {
this.video[0].width = width;
this.video[0].height = height;
}
}
/**
* Returns true if the video is currently playing.
* @return Returns true if the video is playing.
*/
jQuery.HTML5VideoObjectView.prototype.isPlaying = function() {
if (this.video[0]) {
return !this.video[0].paused;
}
}
}
/**
* View for the Semantic Annotation Tool video player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.SemanticAnnotationToolObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the video media object.
*/
jQuery.SemanticAnnotationToolObjectView.prototype.createObject = function() {
var mimeType;
switch (this.model.mediaSource.name) {
case "QuickTime":
mimeType = "video/quicktime"
break;
case "MPEG-4":
case "M4V":
mimeType = "video/mp4";
break;
case "OGG":
mimeType = "video/ogg";
break;
case "3GPP":
mimeType = "video/3gpp";
break;
case "CriticalCommons-LegacyVideo":
var temp = this.model.path.split('//');
if (temp.length == 3) {
var temp2 = temp[2].split('-');
temp2.pop();
var chunk = temp2.join('-');
chunk = chunk.replace(/%20/g, '-');
temp = chunk.split('/');
var slug = temp[temp.length - 1];
var url = 'http://videos.criticalcommons.org/transcoded/http/ccserver.usc.edu/8080/cc/Members/' + chunk + '.mp4/mp4-high/' + slug.toLowerCase() + '-mp4.mp4';
this.model.path = url;
}
mimeType = this.updateCriticalCommonsURLForBrowser();
break;
case "CriticalCommons-Video":
mimeType = this.updateCriticalCommonsURLForBrowser();
break;
case "WebM":
mimeType = "video/webm";
break;
}
var obj = $('').appendTo(this.parentView.mediaContainer);
if ( this.model.options.autoplay && ( this.model.seek == null ) ) {
obj.find( 'video' ).attr( 'autoplay', 'true' );
}
this.video = obj.find('video#'+this.model.filename+'_'+this.model.id);
this.parentView.controllerOffset = 22;
var metadataFunc = function() {
me.parentView.intrinsicDim.x = me.video[0].videoWidth;
me.parentView.intrinsicDim.y = me.video[0].videoHeight;
me.parentView.controllerOffset = 0;
me.parentView.layoutMediaObject();
me.parentView.removeLoadingMessage();
me.setupTool();
}
if (document.addEventListener) {
this.video[0].addEventListener('loadedmetadata', metadataFunc, false);
} else {
this.video[0].attachEvent('onloadedmetadata', metadataFunc);
}
return;
}
jQuery.SemanticAnnotationToolObjectView.prototype.setupTool = function() {
var sat_values = $('.book-title').children('[data-semantic-annotation-tool]').attr('data-semantic-annotation-tool');
var sat_arr = sat_values.split(',');
var sat_address = (sat_arr[0].length) ? sat_arr[0] : 'https://onomy.org/published/95/json';
var sat_language = ('undefined' != typeof(sat_arr[1])) ? sat_arr[1] : 'en';
var parent = $('link#parent').attr('href');
var $video = this.video.first();
// CSS files are loaded in cantaloupe/content.php
var go = function(status) {
var isLoggedIn = parseInt(status.is_logged_in);
var kioskMode = (isLoggedIn) ? false : true;
var username = (isLoggedIn) ? status.fullname : '';
var email = (isLoggedIn) ? status.email : '';
var waldorf = $video.annotate({
serverURL: parent,
tagsURL: sat_address,
onomyLanguage: sat_language,
kioskMode: kioskMode,
cmsUsername: username,
cmsEmail: email,
displayIndex: false,
callback: function(event) {
console.log('Waldorf callback');
}
});
}
// TODO: sometimes $video is undefined, for some reason
console.log($video);
$.getJSON(parent + 'login_status', function(status) {
go(status);
});
}
jQuery.SemanticAnnotationToolObjectView.prototype.updateCriticalCommonsURLForBrowser = function() {
var ext, format, mimeType;
if ( this.model.mediaSource.browserSupport[scalarapi.scalarBrowser] != null ) {
format = this.model.mediaSource.browserSupport[scalarapi.scalarBrowser].format;
}
if (format == 'MPEG-4') {
ext = 'mp4';
mimeType = "video/mp4";
} else {
ext = 'webm';
mimeType = "video/webm";
this.model.path = this.model.path.replace('mp4-high','webm-high');
this.model.path = this.model.path.replace('mp4-low','webm-low');
}
var path_segments = this.model.path.split('.');
if (path_segments.length > 1) {
path_segments[path_segments.length-1] = ext;
this.model.path = path_segments.join('.');
}
return mimeType;
}
/**
* Starts playback of the video.
*/
jQuery.SemanticAnnotationToolObjectView.prototype.play = function() {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
this.video[0].currentTime = this.model.seekAnnotation.properties.start;
}
if (this.video[0].paused) {
this.video[0].play();
}
}
/**
* Pauses playback of the video.
*/
jQuery.SemanticAnnotationToolObjectView.prototype.pause = function() {
if (this.video[0]) {
this.video[0].pause();
}
}
/**
* Seeks to the specified location in the video.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.SemanticAnnotationToolObjectView.prototype.seek = function(time, end) {
if (this.video[0]) {
this.video[0].currentTime = time;
if ('undefined' != typeof(end)) {
$(this.video[0]).trigger('annotationEndTime', [end]); // For Waldorf... assume we're seeking to the beginning of an Annotation
}
}
}
/**
* Returns the current playback position of the video.
* @return The current playback position in seconds.
*/
jQuery.SemanticAnnotationToolObjectView.prototype.getCurrentTime = function() {
if (this.video[0]) {
return this.video[0].currentTime;
}
}
/**
* Resizes the video to the specified dimensions.
*
* @param {Number} width The new width of the video.
* @param {Number} height The new height of the video.
*/
jQuery.SemanticAnnotationToolObjectView.prototype.resize = function(width, height) {
if (this.video) {
this.video[0].width = width;
this.video[0].height = height;
}
}
/**
* Returns true if the video is currently playing.
* @return Returns true if the video is playing.
*/
jQuery.SemanticAnnotationToolObjectView.prototype.isPlaying = function() {
if (this.video[0]) {
return !this.video[0].paused;
}
}
}
/**
* View for the hls.js Audio player to support HTTP Live Streaming.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.HLSAudioObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the hls.js audio media object.
*/
jQuery.HLSAudioObjectView.prototype.createObject = function() {
var HLSObjectId = 'hls_'+this.model.filename+'_'+this.model.id;
obj = $('').appendTo(this.parentView.mediaContainer);
if ( this.model.options.autoplay && ( this.model.seek == null ) ) {
obj.find( 'audio' ).attr( 'autoplay', 'true' );
}
this.audio = obj.find('audio#'+HLSObjectId);
this.parentView.controllerOnly = true;
this.parentView.controllerHeight = 25;
this.parentView.intrinsicDim.y = 45;
// this prevents the top of the player from getting cut off in Firefox
if ( scalarapi.scalarBrowser == "Mozilla" ) {
obj.find( 'audio' ).height( this.parentView.intrinsicDim.y );
}
this.parentView.layoutMediaObject();
this.hls = new Hls({autoStartLoad:false,maxBufferSize:30 * 1000 * 1000,});
this.hls.loadSource(this.model.path);
this.hls.attachMedia(this.audio[0]);
this.parentView.removeLoadingMessage();
if (document.addEventListener) {
this.audio[0].addEventListener('play', () => { me.parentView.startTimer; this.hls.startLoad(startPosition=-1); }, false);
this.audio[0].addEventListener('pause', me.parentView.endTimer, false);
this.audio[0].addEventListener('ended', me.parentView.endTimer, false);
this.audio[0].addEventListener('ended', me.parentView.endTimer, false);
} else {
this.audio[0].attachEvent('play', me.parentView.startTimer);
this.audio[0].attachEvent('pause', me.parentView.endTimer);
this.audio[0].attachEvent('ended', me.parentView.endTimer);
}
return;
}
/**
* Starts playback of the audio.
*/
jQuery.HLSAudioObjectView.prototype.play = function() {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
this.audio[0].currentTime = this.model.seekAnnotation.properties.start;
}
if (this.audio[0].paused) {
this.audio[0].play();
}
}
/**
* Pauses playback of the audio.
*/
jQuery.HLSAudioObjectView.prototype.pause = function() {
this.audio[0].pause();
}
/**
* Seeks to the specified location in the audio.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.HLSAudioObjectView.prototype.seek = function(time) {
this.audio[0].currentTime = time;
}
/**
* Returns the current playback position of the audio.
* @return The current playback position in seconds.
*/
jQuery.HLSAudioObjectView.prototype.getCurrentTime = function() {
return this.audio[0].currentTime;
}
/**
* Resizes the audio to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.HLSAudioObjectView.prototype.resize = function(width) {
$(this.model.element).find('.mediaObject').width(width);
}
/**
* Returns true if the audio is currently playing.
* @return Returns true if the audio is playing.
*/
jQuery.HLSAudioObjectView.prototype.isPlaying = function() {
return !this.audio[0].paused;
}
}
/**
* View for the hls.js Video player to support HTTP Live Streaming.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.HLSVideoObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the hls.js video media object.
*/
jQuery.HLSVideoObjectView.prototype.createObject = function() {
var HLSObjectId = 'hls_'+this.model.filename+'_'+this.model.id;
obj = $('').appendTo(this.parentView.mediaContainer);
if ( this.model.options.autoplay && ( this.model.seek == null ) ) {
obj.find( 'video' ).attr( 'autoplay', 'true' );
}
this.video = obj.find('video#'+HLSObjectId);
var thumbnailURL;
if (this.model.node.thumbnail) {
thumbnailURL = this.model.node.getAbsoluteThumbnailURL();
} else if (this.model.node.current.thumbnail) {
thumbnailURL = this.model.node.current.thumbnail;
}
if (thumbnailURL != undefined) {
this.image = new Image();
$(this.image).on('load', function() {
var $this = $(this);
me.video.attr('poster', $this.attr('src'));
}).attr('src', thumbnailURL);
}
this.parentView.controllerOffset = 22;
var metadataFunc = function() {
me.parentView.controllerOffset = 0;
me.parentView.layoutMediaObject();
me.parentView.removeLoadingMessage();
}
this.parentView.layoutMediaObject();
this.hls = new Hls({autoStartLoad:false,maxBufferSize:30 * 1000 * 1000,});
this.hls.loadSource(this.model.path);
this.hls.attachMedia(this.video[0]);
this.hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
me.parentView.intrinsicDim.x = data.levels[data.levels.length -1].width;
me.parentView.intrinsicDim.y = data.levels[data.levels.length -1].height;
metadataFunc();
});
this.video[0].addEventListener('play', () => { me.parentView.startTimer; this.hls.startLoad(startPosition=-1); }, false);
this.video[0].addEventListener('pause', me.parentView.endTimer, false);
this.video[0].addEventListener('ended', me.parentView.endTimer, false);
return;
}
jQuery.HLSVideoObjectView.prototype.play = function() {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
this.video[0].currentTime = this.model.seekAnnotation.properties.start;
}
if (this.video[0].paused) {
this.video[0].play();
}
}
jQuery.HLSVideoObjectView.prototype.pause = function() {
if (this.video) {
this.video[0].pause();
}
}
/**
* @param {Number} time
*/
jQuery.HLSVideoObjectView.prototype.seek = function(time) {
if (this.video[0]) {
this.video[0].currentTime = time;
}
}
/**
* @return
*/
jQuery.HLSVideoObjectView.prototype.getCurrentTime = function() {
if (this.video[0]) {
return this.video[0].currentTime;
}
}
/**
* @param {Number} width
* @param {Number} height
*/
jQuery.HLSVideoObjectView.prototype.resize = function(width, height) {
if (this.video) {
this.video[0].width = width;
this.video[0].height = height;
}
}
/**
* @return
*/
jQuery.HLSVideoObjectView.prototype.isPlaying = function() {
if (this.video) {
return !this.video[0].paused;
}
}
}
/**
* View for 360 videos, using Google's vrview.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.V360ObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.hasLoaded = false; // has the image loaded?
this.annotations = null; // vrview hotspots
this.annotatedImage = null; // hotspotted image object
/**
* Creates the 360 media object.
*/
jQuery.V360ObjectView.prototype.createObject = function() {
this.hasLoaded = true;
//this.parentView.intrinsicDim.x = 6000;
//this.parentView.intrinsicDim.y = 3000;
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
if ('undefined' == typeof(vrviews)) vrviews = 0; // Global
var my_vrview = parseInt(vrviews);
vrviews++;
this.wrapper = $('');
$(this.wrapper).appendTo(this.parentView.mediaContainer);
var url = this.model.path;
var path = $('link#approot').attr('href')+'views/widgets/vrview/build/';
var obj = {
video: url,
script_path: path
};
$.getScript( path+'vrview.js' , function() {
var vrview_selector = '#vrview_'+my_vrview;
var vrView = new VRView.Player(vrview_selector, obj);
var $play = $('
00:00
');
$(vrview_selector).find('iframe').css('width','100%').css('height', '90%').after($play);
// Center text
var helper_msg = 'Click and drag video to move 360 perspective.';
var width = parseInt($play.width());
if (width < 300) helper_msg = '';
$play.find('.helper_msg').text(helper_msg);
// Play/pause
var $play_button = $play.find('.toggleplay');
$play_button.on('click', function() {
var $this = $(this);
if ('play' == $this.text().toLowerCase()) { // Don't trust vrView.isPlaying
vrView.play();
$this.removeClass('btn-success').addClass('btn-danger').html('Pause').blur();
} else {
vrView.pause();
$this.removeClass('btn-danger').addClass('btn-success').html('Play').blur();
}
});
// Time
var $time = $play.find('.time');
vrView.on('timeupdate', function(e) {
var current = me.formatTime(e.currentTime);
var duration = me.formatTime(e.duration);
$time.text(current);
});
vrView.on('ready', function(e) {
vrView.pause();
});
});
}
jQuery.V360ObjectView.prototype.formatTime = function(time) {
time = !time || typeof time !== 'number' || time < 0 ? 0 : time;
var minutes = Math.floor(time / 60) % 60;
var seconds = Math.floor(time % 60);
minutes = minutes <= 0 ? 0 : minutes;
seconds = seconds <= 0 ? 0 : seconds;
var result = (minutes < 10 ? '0' + minutes : minutes) + ':';
result += seconds < 10 ? '0' + seconds : seconds;
return result;
};
/**
* Resizes the vrview wrapper (in this case, ".mediaContainer" since vrview loads asyncronously) to the specified dimensions.
*
* @param {Number} width The new width of the area.
* @param {Number} height The new height of the area.
*/
jQuery.V360ObjectView.prototype.resize = function(width, height) {
if (width > 500) height = width * 0.75; // Artificially restrict the height
if (height > 400) height = 400; // Artificially cap the height
$(me.parentView.mediaContainer).width(width+'px');
$(me.parentView.mediaContainer).height(height+'px');
}
jQuery.V360ObjectView.prototype.play = function() { }
jQuery.V360ObjectView.prototype.pause = function() { }
jQuery.V360ObjectView.prototype.seek = function(time) { }
jQuery.V360ObjectView.prototype.getCurrentTime = function() { return null; }
jQuery.V360ObjectView.prototype.isPlaying = function() { return true; }
} // !360
/**
* View for the HTML5 audio player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.HTML5AudioObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the media object.
*/
jQuery.HTML5AudioObjectView.prototype.createObject = function() {
var mimeType;
if (this.model.mediaSource.name == 'HLS') {
mimeType = 'application/x-mpegURL';
} else {
mimeType = 'audio/'+this.model.extension;
}
obj = $('').appendTo(this.parentView.mediaContainer);
if ( this.model.options.autoplay && ( this.model.seek == null ) ) {
obj.find( 'audio' ).attr( 'autoplay', 'true' );
}
this.audio = obj.find('audio');
this.parentView.controllerOnly = true;
this.parentView.controllerHeight = 25;
this.parentView.intrinsicDim.y = 45;
// this prevents the top of the player from getting cut off in Firefox
if ( scalarapi.scalarBrowser == "Mozilla" ) {
obj.find( 'audio' ).height( this.parentView.intrinsicDim.y );
}
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
if (document.addEventListener) {
this.audio[0].addEventListener('play', me.parentView.startTimer, false);
this.audio[0].addEventListener('pause', me.parentView.endTimer, false);
this.audio[0].addEventListener('ended', me.parentView.endTimer, false);
this.audio[0].addEventListener('ended', me.parentView.endTimer, false);
} else {
this.audio[0].attachEvent('play', me.parentView.startTimer);
this.audio[0].attachEvent('pause', me.parentView.endTimer);
this.audio[0].attachEvent('ended', me.parentView.endTimer);
}
return;
}
/**
* Starts playback of the audio.
*/
jQuery.HTML5AudioObjectView.prototype.play = function() {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
this.audio[0].currentTime = this.model.seekAnnotation.properties.start;
}
if (this.audio[0].paused) {
this.audio[0].play();
}
}
/**
* Pauses playback of the audio.
*/
jQuery.HTML5AudioObjectView.prototype.pause = function() {
this.audio[0].pause();
}
/**
* Seeks to the specified location in the audio.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.HTML5AudioObjectView.prototype.seek = function(time) {
this.audio[0].currentTime = time;
}
/**
* Returns the current playback position of the audio.
* @return The current playback position in seconds.
*/
jQuery.HTML5AudioObjectView.prototype.getCurrentTime = function() {
return this.audio[0].currentTime;
}
/**
* Resizes the audio to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.HTML5AudioObjectView.prototype.resize = function(width, height) {
$(this.model.element).find('.mediaObject').width(width);
//$(this.model.element).find('.mediaObject').height(height);
}
/**
* Returns true if the audio is currently playing.
* @return Returns true if the audio is playing.
*/
jQuery.HTML5AudioObjectView.prototype.isPlaying = function() {
return !this.audio[0].paused;
}
}
/**
* View for the video player for YouTube videos imported natively.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.YouTubeVideoObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the video media object.
*/
jQuery.YouTubeVideoObjectView.prototype.createObject = function() {
// TODO: the 'origin' param will have to become dynamic for Scalar to be able to live on different servers
obj = $('
').appendTo(this.parentView.mediaContainer);
var queryVars = scalarapi.getQueryVars( this.model.path );
// default player vars
playerVars = {
modestbranding:1,
enablejsapi:1,
origin:'scalar.usc.edu',
rel:0,
autoplay: this.model.options.autoplay ? 1 : 0
}
// overwrite defaults with any params in the youtube url
for ( var prop in queryVars ) {
if ( isNaN( parseFloat( queryVars[ prop ] ) ) ) {
playerVars[ prop ] = queryVars[ prop ];
} else {
// the "t" param needs to get remapped to "start"
if ( prop == "t" ) {
playerVars[ "start" ] = parseFloat( queryVars[ prop ] );
} else {
playerVars[ prop ] = parseFloat( queryVars[ prop ] );
}
}
}
var params = {
videoId: YouTubeGetID( this.model.path ),
playerVars: playerVars,
events: {
'onStateChange': 'onYouTubeStateChange' + this.model.id
}
}
if ( this.model.initialSeekAnnotation != null ) {
params.playerVars.start = Math.round( this.model.initialSeekAnnotation.properties.start );
}
var myId = this.model.id;
// the event doesn't pass back reliable data about which instance it refers so, so we create
// separate event handlers for each instance, named according to the mediaelement id
window[ 'onYouTubeStateChange' + this.model.id ] = function( event ) {
var theMediaelement;
var idString;
// loop through every link and find the media element data whose id matches this instance
$('a').each(function() {
if ($(this).data('clone')) {
var clone = $(this).data('clone');
idString = clone.data('mediaelement').model.id;
if (idString == myId) {
theMediaelement = clone.data('mediaelement');
}
}
if (theMediaelement == undefined) {
if ($(this).data('mediaelement')) {
idString = $(this).data('mediaelement').model.id;
if (idString == myId) {
theMediaelement = $(this).data('mediaelement');
}
}
}
});
// handle video state changes
switch (event.data) {
case YT.PlayerState.PLAYING:
theMediaelement.view.startTimer();
break;
case YT.PlayerState.ENDED:
case YT.PlayerState.PAUSED:
theMediaelement.view.endTimer();
break;
}
}
/*if ((this.model.seekAnnotation != null) && (scalarapi.scalarBrowser != 'MobileSafari')) {
params.playerVars.start = Math.round( this.model.seekAnnotation.properties.start );
params.playerVars.end = Math.round( this.model.seekAnnotation.properties.end );
}*/
this.video = new YT.Player('youtube'+this.model.filename+'_'+this.model.id, params);
this.parentView.removeLoadingMessage();
return;
}
/**
* Starts playback of the YouTube video.
*/
jQuery.YouTubeVideoObjectView.prototype.play = function() {
if (this.video) {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
this.video.seekTo(this.model.seekAnnotation.properties.start, false);
}
if (this.video.getPlayerState() != 1) {
this.video.playVideo();
}
}
}
/**
* Pauses playback of the YouTube video.
*/
jQuery.YouTubeVideoObjectView.prototype.pause = function() {
if (this.video) this.video.pauseVideo();
}
/**
* Seeks to the specified location in the YouTube video.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.YouTubeVideoObjectView.prototype.seek = function(time) {
if (this.video) {
if (this.video.seekTo) {
//var wasPlaying = this.isPlaying();
this.video.seekTo(time, true);
//console.log( 'was playing' + wasPlaying );
//if ( (scalarapi.scalarBrowser != 'MobileSafari') && !wasPlaying ) this.pause(); // because the seek command tries to start playing automagically
}
}
}
/**
* Returns the current playback position of the YouTube video.
* @return The current playback position in seconds.
*/
jQuery.YouTubeVideoObjectView.prototype.getCurrentTime = function() {
if (this.video) {
if (this.video.getCurrentTime) {
return this.video.getCurrentTime();
}
}
return 0;
}
/**
* Resizes the YouTube video to the specified dimensions.
*
* @param {Number} width The new width of the video.
* @param {Number} height The new height of the video.
*/
jQuery.YouTubeVideoObjectView.prototype.resize = function(width, height) {
element = document.getElementById('youtube'+this.model.filename+'_'+this.model.id);
if (this.video) {
this.video.setSize(width - 2, height);
}
}
/**
* Returns true if the video is currently playing.
* @return Returns true if the video is playing.
*/
jQuery.YouTubeVideoObjectView.prototype.isPlaying = function() {
var result = false;
if (this.video) {
if (this.video.getPlayerState) {
result = (this.video.getPlayerState() == 1);
}
}
return result;
}
}
/**
* View for the Vimeo video player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.VimeoVideoObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isVideoPlaying = false; // is the video currently playing?
this.currentTime = 0; // current position of the video
this.initialPauseDone = false; // Vimeo videos need to be played briefly to enable seeking functionality; has it subsequently been paused?
this.percentLoaded = 0; // amount of the video that has loaded
/**
* Creates the video media object.
*/
jQuery.VimeoVideoObjectView.prototype.createObject = function() {
var options = {
id: this.model.filename,
autoplay: this.model.options.autoplay,
title: false,
portrait: false,
byline: false
};
this.id = 'vimeo'+this.model.filename+'_'+this.model.id;
var obj = $('').appendTo(this.parentView.mediaContainer);
this.player = new Vimeo.Player('vimeo'+this.model.filename+'_'+this.model.id, options);
this.player.on('timeupdate', function(data) {
if ((me.model.seekAnnotation != null) && !me.initialPauseDone && !me.parentView.cachedPlayCommand) {
me.player.pause();
}
me.currentTime = data.seconds;
me.initialPauseDone = true;
me.isVideoPlaying = false; // need to set this manually because the pause event isn't firing here
me.currentTime = data.seconds;
});
this.player.on('progress', function(data) {
if (data.lengthComputable) {
me.percentLoaded = data.loaded / parseFloat(data.total);
}
});
this.player.on('play', function() { me.parentView.startTimer(); me.isVideoPlaying = true; });
this.player.on('pause', function() { me.parentView.endTimer(); me.isVideoPlaying = false; });
this.player.on('ended', function() { me.parentView.endTimer(); me.isVideoPlaying = false; });
this.parentView.layoutMediaObject();
Promise.all([this.player.getVideoWidth(), this.player.getVideoHeight()]).then(function(dimensions) {
me.parentView.intrinsicDim.x = dimensions[0];
me.parentView.intrinsicDim.y = dimensions[1];
me.parentView.controllerOffset = 0;
me.parentView.layoutMediaObject();
me.parentView.removeLoadingMessage();
});
return;
}
/**
* Starts playback of the Vimeo video.
*/
jQuery.VimeoVideoObjectView.prototype.play = function() {
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
this.player.setCurrentTime(this.model.seekAnnotation.properties.start);
}
this.player.play();
this.isVideoPlaying = true;
}
/**
* Pauses playback of the Vimeo video.
*/
jQuery.VimeoVideoObjectView.prototype.pause = function() {
this.player.pause();
this.isVideoPlaying = false;
}
/**
* Seeks to the specified location in the Vimeo video.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.VimeoVideoObjectView.prototype.seek = function(time) {
this.player.setCurrentTime(time);
}
/**
* Returns the current playback position of the Vimeo video.
* @return The current playback position in seconds.
*/
jQuery.VimeoVideoObjectView.prototype.getCurrentTime = function() {
return this.currentTime;
}
/**
* Resizes the Vimeo video to the specified dimensions.
*
* @param {Number} width The new width of the video.
* @param {Number} height The new height of the video.
*/
jQuery.VimeoVideoObjectView.prototype.resize = function(width, height) {
video = $('#'+this.id+',#'+this.id+'>iframe');
if (video) {
$(video).width(width);
$(video).height(height);
}
}
/**
* Returns true if the video is currently playing.
* @return Returns true if the video is playing.
*/
jQuery.VimeoVideoObjectView.prototype.isPlaying = function(value, player_id) {
return this.isVideoPlaying;
}
}
/**
* View for the HyperCities map player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.HyperCitiesObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media expands to fill available space
/**
* Creates the image media object.
*/
jQuery.HyperCitiesObjectView.prototype.createObject = function() {
obj = $('').appendTo(this.parentView.mediaContainer);
this.map = obj.find('iframe');
this.parentView.intrinsicDim.x = this.parentView.containerDim.x;
this.parentView.intrinsicDim.y = this.parentView.containerDim.y;
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
return null;
}
// These functions are basically irrelevant for Hypercities
jQuery.HyperCitiesObjectView.prototype.play = function() { }
jQuery.HyperCitiesObjectView.prototype.pause = function() { }
jQuery.HyperCitiesObjectView.prototype.seek = function(time) { }
jQuery.HyperCitiesObjectView.prototype.getCurrentTime = function() { return null; }
jQuery.HyperCitiesObjectView.prototype.isPlaying = function() { return false; }
/**
* Resizes the image to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.HyperCitiesObjectView.prototype.resize = function(width, height) {
if (this.map) {
this.map.width(width+'px');
this.map.height(height+'px');
}
}
}
/**
* View for text.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.TextObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.hasFrameLoaded = false; // has the iframe loaded?
this.isLiquid = true; // media should expand to fill available space
this.currentLine = 0; // user's position in the text
this.textIsPlaying = false; // text is 'played' so that we can seek and show live annotations for it
this.justPlayedSeekAnnotation = false; // did we just play a text annotation?
/**
* Creates the text media object.
*/
jQuery.TextObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
var path = this.model.path;
this.frameId = 'text'+this.model.filename+'_'+this.model.id;
var obj = $('
').appendTo(this.parentView.mediaContainer);
// these styles enable momentum scrolling of iframes for mobile devices;
// if added for desktop browsers they cause double scrollbars
if (( scalarapi.scalarBrowser == 'MobileSafari' ) || ( scalarapi.scalarBrowser == 'Android' )) {
obj.find( 'div' ).css( {
"-webkit-overflow-scrolling": "touch",
"overflow-y": "scroll"
})
}
this.frame = obj.find('#'+this.frameId)[0];
$(this.frame).on("load", function () {
me.codeToList();
me.hasFrameLoaded = true;
me.highlightAnnotatedLines();
me.pause();
if ((me.model.seekAnnotation != null) && !me.justPlayedSeekAnnotation) {
me.parentView.doAutoSeek(me.parentView);
}
});
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
return;
}
/**
* Styles the source code for presentation.
*/
jQuery.TextObjectView.prototype.codeToList = function() {
var extension = scalarapi.getFileExtension(me.model.path);
if (extension != 'txt') {
var code = $(''+$('#'+this.frameId)[0].contentWindow.document.body.innerHTML+'');
code.find('pre').prepend('\n');
code.find('pre').css('white-space', 'pre');
code.find('pre').css('word-wrap', 'normal');
//Convert code elements to ordered lists
code.html("" + code.html().replace(/\n/g, "
").trim() + "
");
} else {
var code = $($('#'+this.frameId)[0].contentWindow.document.body.innerHTML);
code.prepend('\n');
//Convert code elements to ordered lists
code.html("" + code.html().replace(/\n/g, "
").trim() + "
");
//Added styling to make text media feel a bit more like code media (sizing, padding, etc.)
$('#'+this.frameId).contents().find('body ol').css({
'color': 'black',
'text-shadow': '0',
'font-size': '13px',
'padding':'13px 0px'
}).find('li').css('padding','0 13px 0 49.4px');
}
code.find('li').each(function() {
$(this).attr('title', 'Line '+($(this).index()+1));
});
$('#'+this.frameId)[0].contentWindow.document.body.innerHTML = code.html();
var approot = $('link#approot').attr('href');
var cssLink = document.createElement("link")
cssLink.href = approot+'views/widgets/mediaelement/mediaelement_code.css';
cssLink.rel = "stylesheet";
cssLink.type = "text/css";
var doc = $('#'+this.frameId)[0].contentWindow.document;
doc.body.appendChild(cssLink);
// Used http://www.nealgrosskopf.com/tech/thread.php?pid=33 to help out here
}
/**
* Highlights all of the annotated lines in the text.
*/
jQuery.TextObjectView.prototype.highlightAnnotatedLines = function() {
var i;
var n = this.parentView.annotations.length;
var textAnnotations = [];
var annotation;
for (i=0; i 0) this.highlightLinesForAnnotations( textAnnotations, 'highlightLight' );
}
/**
* Highlights the lines of the text associated with the specified annotations.
*/
jQuery.TextObjectView.prototype.highlightLinesForAnnotations = function( textAnnotations, style ) {
if ( style == null ) {
style = 'highlightLight';
}
var li = $('#'+this.frameId).contents().find('li');
li.removeClass( style );
var i;
var n = textAnnotations.length;
var annotation;
for (i=0; i= annotation.properties.start) && ((index+1) <= annotation.properties.end)) {
$(value).addClass(style);
$(value).data('annotation', annotation);
$(value).on('click', function() {
var anno = $(this).data('annotation');
me.currentLine = index + 1;
me.textIsPlaying = true;
me.parentView.doInstantUpdate();
me.highlightLinesForAnnotations( [ anno ], 'highlightDark' );
});
}
});
}
}
/**
* Returns the top-most visible annotation.
* @return The annotation object.
*/
jQuery.TextObjectView.prototype.getVisibleAnnotation = function() {
var theWindow = $('#'+this.frameId)[0].contentWindow;
var docViewTop = $(theWindow).scrollTop();
var docViewBottom = docViewTop + $(theWindow).height();
var li = $('#'+this.frameId).contents().find('li');
var result = null;
var topLineNum = -1;
var i;
var n = li.length;
for (i=0; i= docViewTop) && (elemBottom <= docViewBottom)) {
result = $(elem).data('annotation');
if (topLineNum == -1) {
this.currentLine = i + 1;
}
if (result) {
break;
}
}
}
return result;
}
/**
* Scrolls to the given line.
*
* @param line {Number} The line number to scroll to.
*/
jQuery.TextObjectView.prototype.scrollToLine = function(line) {
if (this.hasFrameLoaded) {
var li = $($('#'+this.frameId)[0].contentWindow.document.body).find('li');
if (li) {
$('#'+this.frameId).contents().find('html,body').stop().animate({scrollTop: $(li[Math.min(Math.max(0, line-2),li.length-1)]).offset().top},'slow');
}
}
}
/**
* Starts playback of the media.
*/
jQuery.TextObjectView.prototype.play = function() {
if (this.model.seekAnnotation != null) {
if (!this.justPlayedSeekAnnotation) {
this.seek(this.model.seekAnnotation.properties.start);
this.justPlayedSeekAnnotation = true;
}
}
this.textIsPlaying = true;
this.parentView.doInstantUpdate();
}
/**
* Pauses playback of the media.
*/
jQuery.TextObjectView.prototype.pause = function() {
this.textIsPlaying = false;
this.parentView.doInstantUpdate();
}
/**
* Seeks to the specified location in the media.
*
* @param {Number} line Destination line number.
*/
jQuery.TextObjectView.prototype.seek = function( line ) {
this.currentLine = line;
this.scrollToLine(line);
var seekAnnotations = [];
var i, annotation,
n = this.parentView.annotations.length;
for ( i = 0; i < n; i++ ) {
annotation = this.parentView.annotations[ i ];
if ( ( this.currentLine >= annotation.properties.start ) && ( this.currentLine <= annotation.properties.end ) ) {
seekAnnotations.push( annotation );
}
}
this.highlightLinesForAnnotations( seekAnnotations, 'highlightDark' );
}
/**
* Returns the current line in the text.
* @return The current line number.
*/
jQuery.TextObjectView.prototype.getCurrentTime = function() {
return this.currentLine;
}
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.TextObjectView.prototype.resize = function(width, height) {
$('#'+this.frameId).parent().width(width);
$('#'+this.frameId).parent().height(height);
}
/**
* Returns true if the media is currently playing.
* @return Returns true if the media is playing.
*/
jQuery.TextObjectView.prototype.isPlaying = function(value, player_id) {
return this.textIsPlaying;
}
}
/**
* View for source code.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.SourceCodeObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.hasLoaded = false; // has the iframe loaded?
this.isLiquid = true; // media should expand to fill available space
this.currentLine = 0; // user's position in the text
this.textIsPlaying = false; // text is 'played' so that we can seek and show live annotations for it
this.justPlayedSeekAnnotation = false; // did we just play a text annotation?
/**
* Creates the source code media object.
*/
jQuery.SourceCodeObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
var path = this.model.path;
this.frameId = 'text'+this.model.filename+'_'+this.model.id;
var queryVars = scalarapi.getQueryVars( path );
var lang = scalarapi.getFileExtension(me.model.path);
if ( queryVars.lang != null ) {
lang = queryVars.lang;
}
this.object = $( '' ).appendTo( this.parentView.mediaContainer );
var temp = $( '
' );
if ( lang != null ) {
temp.addClass( 'language-' + lang );
}
this.object.append( temp );
this.object.on('click', function( e ) {
var i, n, htop, hbottom, highlight,
posY = e.pageY - $(this).offset().top,
highlights = me.object.find( '.line-highlight' );
n = highlights.length;
if ( n > 0 ) {
posY -= parseInt( highlights.eq( 0 ).css( 'margin-top' ) );
}
for ( i = 0; i < n; i++ ) {
highlight = highlights.eq( i );
htop = highlight.position().top + parseInt( highlight.css( 'margin-top' ) );
hbottom = htop + highlight.height();
if (( posY >= htop ) && ( posY <= hbottom )) {
var annotation = me.textAnnotations[ i ];
me.currentLine = annotation.properties.start;
highlight.addClass( 'current' );
me.textIsPlaying = true;
me.parentView.doInstantUpdate();
me.highlightSelectedAnnotations( [ annotation ] );
}
}
} )
me.parentView.intrinsicDim.x = me.parentView.containerDim.x;
me.parentView.intrinsicDim.y = me.parentView.containerDim.y;
me.parentView.layoutMediaObject();
me.parentView.removeLoadingMessage();
var $pre = this.object.find('pre');
var $code = $pre.find('code');
var xhr = new XMLHttpRequest();
xhr.open('GET', $pre.data('src'), true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status < 400 && xhr.responseText) {
$code.get(0).textContent = xhr.responseText;
}
else if (xhr.status >= 400) {
$code.get(0).textContent = '✖ Error ' + xhr.status + ' while fetching file: ' + xhr.statusText;
}
else {
$code.get(0).textContent = '✖ Error: File does not exist or is empty';
}
me.hasLoaded = true;
me.highlightAnnotatedLines();
me.pause();
if ((me.model.seekAnnotation != null) && !me.justPlayedSeekAnnotation) {
me.parentView.doAutoSeek(me.parentView);
}
if(typeof pendingScripts=='undefined'){
pendingScripts = 0;
}
if(--pendingScripts <= 0){
//Do one highlightAll pass after each of the script embeds are loaded - this is because Prism plugins don't seem to run if you just run Prism per element
Prism.highlightAll();
}
}
};
xhr.send(null);
return;
}
/**
* Highlights all of the annotated lines in the text.
*/
jQuery.SourceCodeObjectView.prototype.highlightAnnotatedLines = function() {
var i, annotation
n = this.parentView.annotations.length,
this.textAnnotations = [];
for ( i=0; i 0) this.highlightLinesForAnnotations( this.textAnnotations );
}
/**
* Highlights the specified annotation in a darker color.
*/
jQuery.SourceCodeObjectView.prototype.highlightSelectedAnnotations = function( annotations ) {
var i, annotation, index,
n = this.parentView.annotations.length,
highlights = this.object.find( '.line-highlight' );
highlights.removeClass( 'current' );
for ( i=0; i span' );
if ( rows.length > 0 ) {
$( '#'+this.frameId ).stop().animate( { scrollTop: $( rows[ Math.min( Math.max( 0, line - 1 ), rows.length - 1 ) ] ).position().top },'slow');
}
}
}
/**
* Starts playback of the media.
*/
jQuery.SourceCodeObjectView.prototype.play = function() {
if (this.model.seekAnnotation != null) {
if (!this.justPlayedSeekAnnotation) {
this.seek(this.model.seekAnnotation.properties.start);
this.justPlayedSeekAnnotation = true;
}
}
this.textIsPlaying = true;
this.parentView.doInstantUpdate();
}
/**
* Pauses playback of the media.
*/
jQuery.SourceCodeObjectView.prototype.pause = function() {
this.textIsPlaying = false;
this.parentView.doInstantUpdate();
}
/**
* Seeks to the specified location in the media.
*
* @param {Number} line Destination line number.
*/
jQuery.SourceCodeObjectView.prototype.seek = function( line ) {
this.currentLine = line;
this.scrollToLine(line);
var seekAnnotations = [];
var i, annotation,
n = this.parentView.annotations.length;
for ( i = 0; i < n; i++ ) {
annotation = this.parentView.annotations[ i ];
if ( ( this.currentLine >= annotation.properties.start ) && ( this.currentLine <= annotation.properties.end ) ) {
seekAnnotations.push( annotation );
}
}
this.highlightSelectedAnnotations( seekAnnotations );
}
/**
* Returns the current line in the text.
* @return The current line number.
*/
jQuery.SourceCodeObjectView.prototype.getCurrentTime = function() {
return this.currentLine;
}
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.SourceCodeObjectView.prototype.resize = function(width, height) {
$('#'+this.frameId).parent().width(width);
$('#'+this.frameId).parent().height(height);
}
/**
* Returns true if the media is currently playing.
* @return Returns true if the media is playing.
*/
jQuery.SourceCodeObjectView.prototype.isPlaying = function(value, player_id) {
return this.textIsPlaying;
}
}
/**
* View for rendered HTML content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.HTMLObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.hasFrameLoaded = false; // has the iframe loaded yet?
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the video media object.
*/
jQuery.HTMLObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
this.frameId = 'html'+this.model.filename+'_'+this.model.id;
var obj = $('
').appendTo(this.parentView.mediaContainer);
// these styles enable momentum scrolling of iframes for mobile devices;
// if added for desktop browsers they cause double scrollbars
if (( scalarapi.scalarBrowser == 'MobileSafari' ) || ( scalarapi.scalarBrowser == 'Android' )) {
obj.find( 'div' ).css( {
"-webkit-overflow-scrolling": "touch",
"overflow-y": "scroll"
})
}
this.frame = obj.find('#'+this.frameId)[0];
$(this.frame).on("load", function () {
me.hasFrameLoaded = true;
});
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
$(this.parentView.mediaContainer).parent().find('#viewPageLink').show();
return;
}
// These functions are basically irrelevant for HTML pages
jQuery.HTMLObjectView.prototype.play = function() { }
jQuery.HTMLObjectView.prototype.pause = function() { }
jQuery.HTMLObjectView.prototype.seek = function(time) { }
jQuery.HTMLObjectView.prototype.getCurrentTime = function() { }
jQuery.HTMLObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.HTMLObjectView.prototype.resize = function(width, height) {
$('#'+this.frameId).parent().width(width);
$('#'+this.frameId).parent().height(height);
}
}
/**
* View for PDF documents.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.PDFObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the video media object.
*/
jQuery.PDFObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
this.frameId = 'pdf'+this.model.filename+'_'+this.model.id;
var obj = $('').appendTo(this.parentView.mediaContainer);
this.frame = obj.find('#'+this.frameId)[0];
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
$(this.parentView.mediaContainer).parent().find('#viewPageLink').show();
return;
}
// These functions are basically irrelevant for PDFs
jQuery.PDFObjectView.prototype.play = function() { }
jQuery.PDFObjectView.prototype.pause = function() { }
jQuery.PDFObjectView.prototype.seek = function(time) { }
jQuery.PDFObjectView.prototype.getCurrentTime = function() { }
jQuery.PDFObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.PDFObjectView.prototype.resize = function(width, height) {
$('#'+this.frameId).parent().width(width);
$('#'+this.frameId).parent().height(height);
}
}
/**
* View for Prezi content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.PreziObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the media object.
*/
jQuery.PreziObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
this.frameId = 'prezi'+this.model.filename+'_'+this.model.id;
var obj = $('').appendTo(this.parentView.mediaContainer);
this.frame = obj.find('#'+this.frameId)[0];
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
return;
}
// These functions are basically irrelevant for Prezis
jQuery.PreziObjectView.prototype.play = function() { }
jQuery.PreziObjectView.prototype.pause = function() { }
jQuery.PreziObjectView.prototype.seek = function(time) { }
jQuery.PreziObjectView.prototype.getCurrentTime = function() { }
jQuery.PreziObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.PreziObjectView.prototype.resize = function(width, height) {
$('#'+this.frameId).parent().width(width);
$('#'+this.frameId).parent().height(height);
}
}
/**
* View for unsupported media content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.UnsupportedObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
/**
* Creates the video media object.
*/
jQuery.UnsupportedObjectView.prototype.createObject = function() {
$('
This type of media is unsupported, either by your current browser or by Scalar.
').appendTo(this.parentView.mediaContainer);
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
return;
}
// Irrelevant functions for unsupported media
jQuery.UnsupportedObjectView.prototype.play = function() { }
jQuery.UnsupportedObjectView.prototype.pause = function() { }
jQuery.UnsupportedObjectView.prototype.seek = function(time) { }
jQuery.UnsupportedObjectView.prototype.getCurrentTime = function() { }
jQuery.UnsupportedObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.UnsupportedObjectView.prototype.resize = function(width, height) {
$(this.parentView.mediaContainer).find('.mediaelement_warning').width(width);
$(this.parentView.mediaContainer).find('.mediaelement_warning').height(height);
}
}
/**
* View for the SoundCloud audio player.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.SoundCloudAudioObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isAudioPlaying = false; // is audio currently playing?
this.currentTime = 0; // current position in the audio file
this.isLiquid = true; // media will expand to fill available space
this.initialPauseDone = false; // media must be played briefly for some functions to be available; has it subsequently been paused?
this.widget = null; // SoundClound widget
this.cachedSeekTime = null; // internal storage of last seek request
this.wasPlayingBeforeSeek = false; // was the audio playing before we tried to seek?
/**
* Creates the media object.
*/
jQuery.SoundCloudAudioObjectView.prototype.createObject = function() {
me.mediaObject = $('
').appendTo(this.parentView.mediaContainer);
SC.oEmbed(this.model.path, {auto_play: true, download: true,show_comments:false,liking:false,buying:false,hide_related:true}).then(function(oembed){
if ( oembed != null ) {
oembed.height = me.parentView.resizedDim.y;
me.parentView.controllerHeight = oembed.height;
$(oembed.html).css('height',oembed.height).appendTo(me.mediaObject.children()[0]);
me.widget = SC.Widget($(me.mediaObject).find('iframe')[0]);
// All Sound Cloud Event listeners are extremely unreliable when triggered programatically.
// This motivates the code below as any attempt to exercise fine tuned control over the order
// of events is unadvisable
me.widget.bind(SC.Widget.Events.READY, function() {
me.widget.isPaused(function() {
if(!me.initialPauseDone) {
if ( !me.model.options.autoplay ) {
me.widget.pause();
}
me.initialPauseDone = true;
}
});
});
me.widget.bind(SC.Widget.Events.PLAY, function() {
if(me.initialPauseDone) {
me.parentView.startTimer();
me.isAudioPlaying = true;
}
});
me.widget.bind(SC.Widget.Events.PAUSE, function() {
if(me.initialPauseDone) {
me.parentView.endTimer();
me.isAudioPlaying = false;
}
});
me.widget.bind(SC.Widget.Events.FINISH, function() {
me.parentView.endTimer();
me.isAudioPlaying = false;
});
me.widget.bind(SC.Widget.Events.PLAY_PROGRESS, function(e) {
me.currentTime = e.currentPosition / 1000.0;
});
me.parentView.removeLoadingMessage();
}
});
this.parentView.controllerOnly = true;
this.parentView.controllerHeight = 166;
this.parentView.intrinsicDim.x = this.parentView.containerDim.x;
this.parentView.intrinsicDim.y = this.parentView.containerDim.y;
this.parentView.calculateContainerSize();
this.parentView.layoutMediaObject();
return;
}
/**
* Starts playback of the media.
*/
jQuery.SoundCloudAudioObjectView.prototype.play = function() {
me.widget.setVolume(100);
if ((this.model.seekAnnotation != null) && (!this.parentView.overrideAutoSeek)) {
if (!this.isAudioPlaying) {
this.widget.play();
}
this.wasPlayingBeforeSeek = true;
this.cachedSeekTime = this.model.seekAnnotation.properties.start * 1000;
} else {
if (!this.isAudioPlaying) {
this.widget.play();
}
}
}
/**
* Pauses playback of the media.
*/
jQuery.SoundCloudAudioObjectView.prototype.pause = function() {
this.widget.pause();
}
/**
* Seeks to the specified location in the media.
*
* @param {Number} time Seek location in seconds.
*/
jQuery.SoundCloudAudioObjectView.prototype.seek = function(time) {
this.widget.seekTo(time * 1000);
this.currentTime = time;
}
/**
* Returns the current playback position of the media.
* @return The current playback position in seconds.
*/
jQuery.SoundCloudAudioObjectView.prototype.getCurrentTime = function() {
return this.currentTime;
}
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.SoundCloudAudioObjectView.prototype.resize = function(width, height) {
var theElement = $('#soundcloud'+this.model.filename+'_'+this.model.id);
if (theElement) {
$(theElement).width(width);
$(theElement).height(height);
}
}
/**
* Returns true if the video is currently playing.
* @return Returns true if the media is playing.
*/
jQuery.SoundCloudAudioObjectView.prototype.isPlaying = function() {
return this.isAudioPlaying;
}
}
/**
* View for Google Maps content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.GoogleMapsObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the video media object.
*/
jQuery.GoogleMapsObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
this.mediaObject = $('').appendTo(this.parentView.mediaContainer);
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
var mapOptions = {
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById('googlemaps'+me.model.id), mapOptions);
var kmlLayer = new google.maps.KmlLayer(this.model.path);
kmlLayer.setMap(map);
return;
}
// These functions are basically irrelevant for HTML pages
jQuery.GoogleMapsObjectView.prototype.play = function() { }
jQuery.GoogleMapsObjectView.prototype.pause = function() { }
jQuery.GoogleMapsObjectView.prototype.seek = function(time) { }
jQuery.GoogleMapsObjectView.prototype.getCurrentTime = function() { }
jQuery.GoogleMapsObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.GoogleMapsObjectView.prototype.resize = function(width, height) {
$('#googlemaps'+me.model.id).width(Math.round(width));
$('#googlemaps'+me.model.id).height(Math.round(height));
}
}
/**
* View for Deep Zoom Image content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.DeepZoomImageObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the video media object.
*/
jQuery.DeepZoomImageObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
var path = this.model.path;
this.mediaObject = $( '' ).appendTo( this.parentView.mediaContainer );
this.viewer = OpenSeadragon( {
id: "openseadragon" + this.model.id,
prefixUrl: approot + "views/widgets/mediaelement/openseadragon/images/",
showNavigator: true,
navigationControlAnchor: OpenSeadragon.ControlAnchor.BOTTOM_RIGHT,
tileSources: path
} );
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
return;
}
// These functions are basically irrelevant for this type of media
jQuery.DeepZoomImageObjectView.prototype.play = function() { }
jQuery.DeepZoomImageObjectView.prototype.pause = function() { }
jQuery.DeepZoomImageObjectView.prototype.seek = function(time) { }
jQuery.DeepZoomImageObjectView.prototype.getCurrentTime = function() { }
jQuery.DeepZoomImageObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.DeepZoomImageObjectView.prototype.resize = function(width, height) {
$('#openseadragon'+me.model.id).width(Math.round(width));
$('#openseadragon'+me.model.id).height(Math.round(height));
}
}
/**
* View for IIIF manifest (Mirador) content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.MiradorObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the video media object.
*/
jQuery.MiradorObjectView.prototype.createObject = function() {
this.mediaObject = $( ``).appendTo( this.parentView.mediaContainer );
var miradorInstance = Mirador.viewer({
id: `mirador-${me.model.id}`,
windows: [{ manifestId: this.model.node.current.sourceFile }]
});
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
// make sure mirador doesn't overflow its bounds
$('.mirador-viewer', this.mediaObject).css('max-height', $(this.parentView.mediaContainer).css('max-height'));
return;
}
// These functions are basically irrelevant for this type of media
jQuery.MiradorObjectView.prototype.play = function() { }
jQuery.MiradorObjectView.prototype.pause = function() { }
jQuery.MiradorObjectView.prototype.seek = function(time) { }
jQuery.MiradorObjectView.prototype.getCurrentTime = function() { }
jQuery.MiradorObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.MiradorObjectView.prototype.resize = function(width, height) {
$(`#mirador-${me.model.id}`).width(Math.round(width));
$(`#mirador-${me.model.id}`).height(Math.round(height));
}
}
/**
* View for Threejs content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.ThreejsObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the media object.
*/
jQuery.ThreejsObjectView.prototype.createObject = function() {
this.mediaObject = $( '' ).appendTo( this.parentView.mediaContainer );
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
var params = {};
if (this.model.node.current.auxProperties["dcterms:spatial"] != null) {
params = JSON.parse(unescape(this.model.node.current.auxProperties["dcterms:spatial"][0]));
}
this.camera = new THREE.PerspectiveCamera(60, this.mediaObject.width() / this.mediaObject.height(), 1, 1000);
this.camera.position.z = 10;
/*if (params.camera != null) {
if (params.camera.fov != null) this.camera.setFocalLength(params.camera.fov);
if (params.camera.position != null) {
this.camera.position.fromArray(params.camera.position);
}
if (params.camera.rotation != null) {
this.camera.rotation.fromArray(this.degreesArrayToRadians(params.camera.rotation));
}
}*/
this.controls = new THREE.TrackballControls(this.camera, this.mediaObject[0]);
this.controls.addEventListener('change', function() {
me.renderer.render(me.scene, me.camera);
});
this.scene = new THREE.Scene();
var loader = new THREE.STLLoader();
loader.load(this.model.path, function(geometry) {
var material = new THREE.MeshLambertMaterial({color:0xffffff, shading: THREE.FlatShading});
var mesh = new THREE.Mesh(geometry, material);
me.scene.add(mesh);
if (params.model != null) {
if (params.model.position != null) {
mesh.position.fromArray(params.model.position);
}
if (params.model.rotation != null) {
mesh.rotation.fromArray(me.degreesArrayToRadians(params.model.rotation));
}
if (params.model.scale != null) {
mesh.scale.fromArray(params.model.scale);
}
}
me.renderer.render(me.scene, me.camera);
});
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(1, 1, 1);
this.scene.add(light);
light = new THREE.DirectionalLight(0x2069a8);
light.position.set(-1, -1, -1);
this.scene.add(light);
light = new THREE.AmbientLight(0x222222);
this.scene.add(light);
this.renderer = new THREE.WebGLRenderer({antialias: false});
this.renderer.setClearColor(0x444444, 1);
this.renderer.setSize(this.mediaObject.width(), this.mediaObject.height());
this.mediaObject.append(this.renderer.domElement);
this.animate = function animate() {
requestAnimationFrame(me.animate);
me.controls.update();
}
this.renderer.render(this.scene, this.camera);
this.animate();
return;
}
// These functions are basically irrelevant for this type of media
jQuery.ThreejsObjectView.prototype.play = function() { }
jQuery.ThreejsObjectView.prototype.pause = function() { }
jQuery.ThreejsObjectView.prototype.seek = function(time) { }
jQuery.ThreejsObjectView.prototype.getCurrentTime = function() { }
jQuery.ThreejsObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.ThreejsObjectView.prototype.resize = function(width, height) {
$('#threejs'+me.model.id).width(Math.round(width));
$('#threejs'+me.model.id).height(Math.round(height));
}
jQuery.ThreejsObjectView.prototype.degreesArrayToRadians = function(degArray) {
return [this.degreesToRadians(degArray[0]),this.degreesToRadians(degArray[1]),this.degreesToRadians(degArray[2])]
}
jQuery.ThreejsObjectView.prototype.degreesToRadians = function(degrees) {
return (degrees / 360.0) * (Math.PI * 2);
}
}
/**
* View for culturally sensitive content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.CulturallySensitiveObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the media object.
*/
jQuery.CulturallySensitiveObjectView.prototype.createObject = function() {
this.mediaObject = $( '
'+
''+
'
Protected: Respecting Cultural Protocols Information about this item can be shared publicly, but the media itself cannot be made publicly available, in accordance with cultural values, beliefs, or protocols that define its access and use. Learn more about appropriate ways of interacting with Indigenous cultural heritage materials and the labels used in this publication here.
'+
'
' ).appendTo( this.parentView.mediaContainer );
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
return;
}
// These functions are basically irrelevant for this type of media
jQuery.CulturallySensitiveObjectView.prototype.play = function() { }
jQuery.CulturallySensitiveObjectView.prototype.pause = function() { }
jQuery.CulturallySensitiveObjectView.prototype.seek = function(time) { }
jQuery.CulturallySensitiveObjectView.prototype.getCurrentTime = function() { }
jQuery.CulturallySensitiveObjectView.prototype.isPlaying = function(value, player_id) { return null; }
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.CulturallySensitiveObjectView.prototype.resize = function(width, height) {
$('#culturallysensitive'+me.model.id).width(Math.round(width));
$('#culturallysensitive'+me.model.id).height(Math.round(height));
console.log(width+' '+height);
var theElement = document.getElementById('culturallysensitive'+this.model.id);
if (theElement) {
if (width < 320) {
$(theElement).addClass('small');
} else {
$(theElement).removeClass('small');
}
}
}
}
/**
* View for ArcGIS content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.ArcGISObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.isLiquid = true; // media will expand to fill available space
jQuery.ArcGISObjectView.prototype.createObject = function() {
queryVars = scalarapi.getQueryVars(this.model.path);
if (queryVars.webscene != null) {
this.mediaObject = $('').appendTo(this.parentView.mediaContainer);
require([
"esri/Map",
"esri/WebScene",
"esri/views/ui/DefaultUI",
"esri/views/SceneView",
"esri/Camera",
"esri/webscene/InitialViewProperties"
], function(Map, WebScene, DefaultUI, SceneView, Camera, InitialViewProperties) {
var scene = new WebScene({
portalItem: {
id: queryVars.webscene
}
});
var properties = {
map: scene,
container: 'arcgis'+me.model.id
};
if (queryVars.viewpoint != null) {
if (queryVars.viewpoint.indexOf('cam:') != -1) {
var a = queryVars.viewpoint.substr(4).split(';');
pos = a[0].split(',');
ht = a[1].split(',');
properties.camera = {
position: {
longitude: pos[0],
latitude: pos[1],
z: pos[2]
},
heading: ht[0],
tilt: ht[1]
}
if (pos.length == 4) {
properties.camera.position.spatialReference = { wkid: pos[3] };
}
}
}
me.camera = new Camera();
me.sceneView = new SceneView(properties);
});
this.parentView.removeLoadingMessage();
}
this.parentView.layoutMediaObject();
return;
}
// These functions are basically irrelevant for this type of media
jQuery.ArcGISObjectView.prototype.play = function() { }
jQuery.ArcGISObjectView.prototype.pause = function() { }
jQuery.ArcGISObjectView.prototype.getCurrentTime = function() { }
jQuery.ArcGISObjectView.prototype.getPosition3D = function() {
return {
"latitude": this.sceneView.camera.position.latitude,
"longitude": this.sceneView.camera.position.longitude,
"altitude": this.sceneView.camera.position.z,
"heading": this.sceneView.camera.heading,
"tilt": this.sceneView.camera.tilt,
"fieldOfView": this.sceneView.camera.fov
}
}
jQuery.ArcGISObjectView.prototype.isPlaying = function(value, player_id) { return null; }
jQuery.ArcGISObjectView.prototype.seek = function(transform) {
this.camera.position = {
latitude: transform.latitude,
longitude: transform.longitude,
z: transform.altitude
}
this.camera.heading = transform.heading;
this.camera.tilt = transform.tilt;
this.camera.fieldOfView = transform.fieldOfView;
this.sceneView.goTo(this.camera);
}
jQuery.ArcGISObjectView.prototype.resize = function(width, height) {
$('#arcgis'+me.model.id).width(Math.round(width));
$('#arcgis'+me.model.id).height(Math.round(height));
}
}
/**
* View for rendered HTML content.
* @constructor
*
* @param {Object} model Instance of the model.
* @param {Object} parentView Primary view for the media element.
*/
jQuery.UnityWebGLObjectView = function(model, parentView) {
var me = this;
this.model = model; // instance of the model
this.parentView = parentView; // primary view for the media element
this.hasFrameLoaded = false; // has the iframe loaded yet?
this.isLiquid = true; // media will expand to fill available space
/**
* Creates the video media object.
*/
jQuery.UnityWebGLObjectView.prototype.createObject = function() {
var approot = $('link#approot').attr('href');
this.frameId = 'html'+this.model.filename+'_'+this.model.id;
var obj = $('
').appendTo(this.parentView.mediaContainer);
this.frame = obj.find('#'+this.frameId)[0];
$(this.frame).bind("load", function () {
me.hasFrameLoaded = true;
me.receiver = this.contentWindow;
});
this.parentView.layoutMediaObject();
this.parentView.removeLoadingMessage();
$(this.parentView.mediaContainer).parent().find('#viewPageLink').show();
return;
}
// These functions are basically irrelevant for this type of media
jQuery.UnityWebGLObjectView.prototype.play = function() { }
jQuery.UnityWebGLObjectView.prototype.pause = function() { }
jQuery.UnityWebGLObjectView.prototype.getCurrentTime = function() { }
jQuery.UnityWebGLObjectView.prototype.getPosition3D = function() {
this.receiver.postMessage({
"objectName": "ScalarCamera",
"methodName": "GetTransform",
"message": null
});
}
jQuery.UnityWebGLObjectView.prototype.isPlaying = function(value, player_id) { return null; }
jQuery.UnityWebGLObjectView.prototype.seek = function(point_3d) {
this.receiver.postMessage({
"objectName": "ScalarCamera",
"methodName": "SetTransform",
"message": point_3d
});
}
jQuery.UnityWebGLObjectView.prototype.sendMessage = function(message) {
this.receiver.postMessage({
"objectName": "ScalarCamera",
"methodName": "HandleMessage",
"message": message
});
}
jQuery.UnityWebGLObjectView.prototype.handleAnnotationsUpdated = function(slugs) {
console.log('handleAnnotationsUpdated');
this.receiver.postMessage({
"objectName": "ScalarCamera",
"methodName": "HandleAnnotationsUpdated",
"message": slugs
});
}
/**
* Resizes the media to the specified dimensions.
*
* @param {Number} width The new width of the media.
* @param {Number} height The new height of the media.
*/
jQuery.UnityWebGLObjectView.prototype.resize = function(width, height) {
$('#'+this.frameId).parent().width(width);
$('#'+this.frameId).parent().height(height);
}
}
}) (jQuery);