/**
* @module Player
*/
/**
* I am the HypervideoModel which stores all data which make up the hypervideo.
*
* @class HypervideoModel
* @static
*/
FrameTrail.defineModule('HypervideoModel', function(){
var hasHTML5Video = true,
duration = 0,
sourceFiles = {
webm: '',
mp4: ''
},
hypervideoName = '',
description = '',
creator = '',
creatorId = '',
created = 0,
lastchanged = 0,
hidden = false,
subtitleFiles = [],
subtitles = [],
selectedLang = '',
overlays = [],
videolinks = [],
annotationSets = {},
selectedAnnotationSet = '',
mainAnnotationSet = '',
unsavedOverlays = false,
unsavedVideolinks = false,
unsavedAnnotations = false;
/**
* The data model is initialized after the {{#crossLink "Database"}}Database{{/crossLink}} is ready
* and before the different views (like {{#crossLink "ViewVideo"}}ViewVideo{{/crossLink}}) are created.
*
* I do the following jobs:
* * I read in the {{#crossLink "Database/hypervideo:attribute"}}hypervideo metadata{{/crossLink}}, and store them in my attributes (like name, description, creator)
* * I read in the {{#crossLink "Database/hypervideo:attribute"}}configuration of the hypervideo{{/crossLink}} (hypervideo.config) and set the key-value-pairs as global state (FrameTrail.changeState())
* * I read in the sequence data of the hypervideo, and set the video source file (webm and mp4), or – when their is no resourceId for a video – I set the {{#crossLink "HypervideoModel/duration:attribute"}}duration{{/crossLink}} attribute for a "null video".
* * I call {{#crossLink "HypervideoModel/initModelOfOverlays:method"}}initModelOfOverlays{{/crossLink}}, {{#crossLink "HypervideoModel/initModelOfVideolinks:method"}}initModelOfVideolinks{{/crossLink}} and {{#crossLink "HypervideoModel/initModelOfAnnotations:method"}}initModelOfAnnotations{{/crossLink}}.
* * I return control to the callback.
*
* @method initModel
* @param {Function} callback
*/
function initModel(callback) {
var database = FrameTrail.module('Database'),
hypervideo = database.hypervideo,
videoData = database.sequence.clips[0];
// Read in metadata
hypervideoName = hypervideo.name;
description = hypervideo.description;
creator = hypervideo.creator;
creatorId = hypervideo.creatorId;
created = hypervideo.created;
lastchanged = hypervideo.lastchanged;
hidden = hypervideo.hidden;
// Read in config of Hypervideo
for (var key in hypervideo.config) {
FrameTrail.changeState('hv_config_' + key, hypervideo.config[key]);
}
// Set video source or NullVideo
if (!videoData.resourceId) {
hasHTML5Video = false;
duration = videoData.duration;
} else {
sourceFiles.webm = database.resources[videoData.resourceId].src;
sourceFiles.mp4 = database.resources[videoData.resourceId].attributes.alternateVideoFile;
}
// Set subtitle files
subtitleFiles = hypervideo.subtitles;
initModelOfOverlays(database);
initModelOfVideolinks(database);
initModelOfAnnotations(database);
initModelOfSubtitles(database);
// Show warning if user tries to leave the page without having saved changes
$(window).on('beforeunload', function(e) {
if ( FrameTrail.getState('unsavedChanges') ) {
// This message is not actually shown to the user in most cases, but the browser needs a return value
var message = "You have not saved your changes. Are you sure you want to leave the page?";
return message;
}
});
callback.call()
};
/**
* I create the {{#crossLink "Overlay"}}Overlay{{/crossLink}} objects from the data in the {{#crossLink "Database"}}Database{{/crossLink}} and store them
* in my {{#crossLink "HypervideoModel/overlays:attribute"}}overlays{{/crossLink}} attribute.
*
* @method initModelOfOverlays
* @param {Database} database
* @private
*/
function initModelOfOverlays(database) {
for (var idx in database.overlays) {
overlays.push(
FrameTrail.newObject('Overlay',
database.overlays[idx]
)
);
}
};
/**
* I create the {{#crossLink "Videolink"}}Videolink{{/crossLink}} objects from the data in the {{#crossLink "Database"}}Database{{/crossLink}} and store them
* in my {{#crossLink "HypervideoModel/videolinks:attribute"}}videolinks{{/crossLink}} attribute.
*
* @method initModelOfVideolinks
* @param {Database} database
* @private
*/
function initModelOfVideolinks(database) {
for (var idx in database.links) {
videolinks.push(
FrameTrail.newObject('Videolink',
database.links[idx]
)
);
}
};
/**
* I create the {{#crossLink "Annotation"}}Annotation{{/crossLink}} objects from the data in the {{#crossLink "Database"}}Database{{/crossLink}} and store them
* in my {{#crossLink "HypervideoModel/annotations:attribute"}}videolinks{{/crossLink}} attribute.
*
* Also I select the the main annotation set (from the user who created the hypervideo) as the current one.
*
* @method initModelOfAnnotations
* @param {Database} database
* @private
*/
function initModelOfAnnotations(database) {
// clear previous data
annotationSets = {};
for (var ownerId in database.annotations) {
annotationSets[ownerId] = [];
for (var idx in database.annotations[ownerId]) {
annotationSets[ownerId].push(
FrameTrail.newObject('Annotation',
database.annotations[ownerId][idx]
)
);
}
}
for (var ownerId in database.annotationfileIDs) {
if (database.annotationfileIDs[ownerId] === FrameTrail.module('Database').hypervideo.mainAnnotation) {
selectedAnnotationSet = mainAnnotationSet = ownerId;
}
}
};
/**
* I create the {{#crossLink "Subtitle"}}Subtitle{{/crossLink}} objects from the data in the {{#crossLink "Database"}}Database{{/crossLink}} and store them
* in my {{#crossLink "HypervideoModel/subtitles:attribute"}}subtitles{{/crossLink}} attribute.
*
* @method initModelOfSubtitles
* @param {Database} database
*/
function initModelOfSubtitles(database) {
for (var lang in database.subtitles) {
subtitles[lang] = [];
for (var idx in database.subtitles[lang].cues) {
subtitles[lang].push(
FrameTrail.newObject('Subtitle',
database.subtitles[lang].cues[idx]
)
);
}
}
if (subtitles['en']) {
selectedLang = 'en';
} else if ( !$.isEmptyObject(database.subtitles) ) {
for (first in database.subtitles) break;
selectedLang = first;
}
};
/**
* I remove all data of an overlay from the model and from the database.
*
* I am called from {{#crossLink "OverlaysController/deleteOverlay:method"}}OverlaysController/deleteOverlay{{/crossLink}}.
*
* @method removeOverlay
* @param {Overlay} overlay
*/
function removeOverlay(overlay) {
var idx;
idx = overlays.indexOf(overlay);
overlays.splice(idx, 1);
idx = FrameTrail.module('Database').overlays.indexOf(overlay.data);
FrameTrail.module('Database').overlays.splice(idx, 1);
newUnsavedChange('overlays');
};
/**
* I remove all data of a video link from the model and from the database.
*
* I am called from {{#crossLink "VideolinksController/deleteVideolink:method"}}VideolinksController/deleteVideolink{{/crossLink}}.
*
* @method removeVideolink
* @param {Videolink} videolink
*/
function removeVideolink(videolink) {
var idx;
idx = videolinks.indexOf(videolink);
videolinks.splice(idx, 1);
idx = FrameTrail.module('Database').links.indexOf(videolink.data);
FrameTrail.module('Database').links.splice(idx, 1);
newUnsavedChange('links');
};
/**
* I remove all data of an annotation from the model and from the database.
*
* I am called from {{#crossLink "AnnotationsController/deleteAnnotation:method"}}AnnotationsController/deleteAnnotation{{/crossLink}}.
*
* @method removeAnnotation
* @param {Annotation} annotation
*/
function removeAnnotation(annotation) {
var database = FrameTrail.module('Database'),
idx;
idx = annotationSets[selectedAnnotationSet].indexOf(annotation);
annotationSets[selectedAnnotationSet].splice(idx, 1);
if (database.annotations[selectedAnnotationSet]) {
idx = database.annotations[selectedAnnotationSet].indexOf(annotation.data);
database.annotations[selectedAnnotationSet].splice(idx, 1);
}
newUnsavedChange('annotations');
};
/**
* I create a new {{#crossLink "Overlay"}}overlay{{/crossLink}}.
*
* I am called from {{#crossLink "OverlaysController/makeTimelineDroppable:method"}}OverlaysController{{/crossLink}}.
*
* @method newOverlay
* @param {} protoData
* @return Overlay
*/
function newOverlay(protoData) {
var resourceDatabase = FrameTrail.module('Database').resources,
newOverlay,
newData;
// TODO: clean code
if ( protoData.type == 'text' ) {
newData = {
"name": protoData.name,
"creator": FrameTrail.getState('username'),
"creatorId": FrameTrail.module('UserManagement').userID,
"created": Date.now(),
"type": protoData.type,
"src": '',
"start": protoData.start,
"end": protoData.end,
"attributes": protoData.attributes,
"position": {
"top": protoData.position.top,
"left": protoData.position.left,
"width": 30,
"height": 30
}
}
} else {
newData = {
"name": resourceDatabase[protoData.resourceId].name,
"creator": FrameTrail.getState('username'),
"creatorId": FrameTrail.module('UserManagement').userID,
"created": Date.now(),
"type": resourceDatabase[protoData.resourceId].type,
"src": resourceDatabase[protoData.resourceId].src,
"thumb": resourceDatabase[protoData.resourceId].thumb,
"start": protoData.start,
"end": protoData.end,
"resourceId": protoData.resourceId,
"attributes": resourceDatabase[protoData.resourceId].attributes,
"position": {
"top": protoData.position.top,
"left": protoData.position.left,
"width": 30,
"height": 30
}
}
}
FrameTrail.module('Database').overlays.push(newData);
newOverlay = FrameTrail.newObject('Overlay', newData)
overlays.push(newOverlay);
newUnsavedChange('overlays');
return newOverlay;
};
/**
* I create a new {{#crossLink "Videolink"}}video link{{/crossLink}}.
*
* I am called from {{#crossLink "VideolinksController/makeTimelineDroppable:method"}}VideolinksController{{/crossLink}}.
*
* @method newVideolink
* @param {} protoData
* @return Videolink
*/
function newVideolink(protoData) {
var newVideolink,
newData = {
"name": protoData.name,
"creator": FrameTrail.getState('username'),
"creatorId": FrameTrail.module('UserManagement').userID,
"created": Date.now(),
"href": protoData.href,
"start": protoData.start,
"end": protoData.end,
"attributes": {}
};
FrameTrail.module('Database').links.push(newData);
newVideolink = FrameTrail.newObject('Videolink', newData)
videolinks.push(newVideolink);
newUnsavedChange('links');
return newVideolink;
};
/**
* I create a new {{#crossLink "Annotation"}}annotation{{/crossLink}}.
*
* I am called from {{#crossLink "AnnotationsController/makeTimelineDroppable:method"}}AnnotationsController{{/crossLink}}.
*
* @method newAnnotation
* @param {} protoData
* @return Annotation
*/
function newAnnotation(protoData) {
var newAnnotation,
database = FrameTrail.module('Database'),
resourceDatabase = database.resources,
ownerId = FrameTrail.module('UserManagement').userID,
newData = {
"name": resourceDatabase[protoData.resourceId].name,
"creator": FrameTrail.getState('username'),
"creatorId": FrameTrail.module('UserManagement').userID,
"created": Date.now(),
"type": resourceDatabase[protoData.resourceId].type,
"src": resourceDatabase[protoData.resourceId].src,
"thumb": resourceDatabase[protoData.resourceId].thumb,
"start": protoData.start,
"end": protoData.end,
"resourceId": protoData.resourceId,
"attributes": resourceDatabase[protoData.resourceId].attributes
};
if (!database.annotations[ownerId]) {
database.annotations[ownerId] = []
}
FrameTrail.module('Database').annotations[ownerId].push(newData);
if (!annotationSets[ownerId]) {
annotationSets[ownerId] = []
}
newAnnotation = FrameTrail.newObject('Annotation', newData);
annotationSets[ownerId].push(newAnnotation);
newUnsavedChange('annotations');
return newAnnotation;
};
/**
* When the {{#crossLinks "HypervideoModel/videolinks:attribute"}}attribute videolinks{{/crossLinks}} is accessed,
* it needs to return the video link objects in an array, which is sorted by the start time. This is what I do.
*
* @method getVideolinks
* @return Array of Videolinks
* @private
*/
function getVideolinks() {
return videolinks.sort(function(a, b){
if(a.data.start > b.data.start) {
return 1;
} else if(a.data.start < b.data.start) {
return -1;
} else {
return 0;
}
});
};
/**
* Needed for the {{#crossLinks "HypervideoModel/annotationSets:attribute"}}annotationSets attribute{{/crossLinks}}.
* This attribute' purpose is to tell, what users have an annotationfile for the current hypervideo.
*
* I return an array of maps in the format
*
* [ { id: ownerid, name: ownerName }, ... ]
*
*
* @method getAnnotationSets
* @return Array of { id: ownerId, name: ownerName}
* @private
*/
function getAnnotationSets() {
var database = FrameTrail.module('Database'),
ids = [],
ownerName,
ownerColor,
hypervideoIndexItem,
annotationfileId;
for (var ownerId in annotationSets) {
annotationfileId = database.annotationfileIDs[ownerId];
hypervideoIndexItem = database.hypervideo.annotationfiles[annotationfileId];
if (hypervideoIndexItem) {
ownerName = hypervideoIndexItem.owner;
ownerColor = FrameTrail.module('Database').users[ownerId].color;
} else if (ownerId === FrameTrail.module('UserManagement').userID) {
ownerName = FrameTrail.getState('username');
ownerColor = FrameTrail.getState('userColor');
} else {
ownerName = 'unknown';
ownerColor = 'FFFFFF';
}
ids.push({
id: ownerId,
name: ownerName,
color: ownerColor
});
}
return ids;
};
/**
* When the {{#crossLinks "HypervideoModel/annotations:attribute"}}attribute annotations{{/crossLinks}} is accessed,
* it needs to return an array of the currently selected annotation set (choosen by assigning the annotation's ownerId to {{#crossLinks "HypervideoModel/annotationSet:attribute"}}annotationSet{{/crossLinks}}).
* The array needs to be sorted by the start time.
*
* @method getAnnotations
* @return Array of Annotations
* @private
*/
function getAnnotations() {
return annotationSets[selectedAnnotationSet].sort(function(a, b){
if(a.data.start > b.data.start) {
return 1;
} else if(a.data.start < b.data.start) {
return -1;
} else {
return 0;
}
});
};
/**
* I am needed by the {{#crossLinks "HypervideoModel/annotationSet:attribute"}}annotationSet attribute{{/crossLinks}}.
*
* My parameter can be set in three ways:
* * when the argument is null, I select the main annotation file (from the hypervideo's _index.json entry)
* * when the special string '#myAnnotationSet' is given as argument, I select the logged-in user's ID
* * an all other cases, I take the literal string as the ID to select.
*
* When my user changes the currently selected annotation sets, I have to assure, that both myself and the
* {{#crossLinks "Database"}}Database{{/crossLinks}} have under the respective attribute name an [Array] present, for
* manipulating annotation objects inside them.
*
* @method selectAnnotationSet
* @param {String or null} anID
* @return String
* @private
*/
function selectAnnotationSet(anID) {
var database = FrameTrail.module('Database'),
selectID;
if (anID === null) {
return selectedAnnotationSet = mainAnnotationSet;
}
if (anID === '#myAnnotationSet') {
selectID = FrameTrail.module('UserManagement').userID;
} else {
selectID = anID;
}
if (!annotationSets.hasOwnProperty(selectID)) {
annotationSets[selectID] = [];
}
if (!database.annotations.hasOwnProperty(selectID)) {
database.annotations[selectID] = [];
}
return selectedAnnotationSet = selectID;
};
/**
* When the {{#crossLinks "HypervideoModel/subtitles:attribute"}}attribute subtitles{{/crossLinks}} is accessed,
* it needs to return an array of the currently selected language subtitles (choosen by assigning the selected language to {{#crossLinks "HypervideoModel/selectedLang:attribute"}}selectedLang{{/crossLinks}}).
*
* @method getSubtitles
* @return Object containing the language label and an Array of Subtitles
* @private
*/
function getSubtitles() {
return subtitles[selectedLang];
};
/**
* I serve the purpose to set markers (both visually and in my data model),
* in which categories (overlays, videolinks, annotations) the user has unsaved changes.
*
* @method newUnsavedChange
* @param {String} category
*/
function newUnsavedChange(category) {
if (category === 'overlays') {
unsavedOverlays = true;
} else if (category === 'links') {
unsavedVideolinks = true;
} else if (category === 'annotations') {
unsavedAnnotations = true;
}
FrameTrail.module('Sidebar').newUnsavedChange(category);
FrameTrail.changeState('unsavedChanges', true);
}
/**
* I am the central function for saving changes back to the server.
*
* I save only, what is necessary (overlays, videolinks, annotations).
*
* When all saving requests to the server have completed, I check all their responses.
* If there where any errors I display them and abort. Otherwise I reset the
* "unsavedChanges"-markers back to false and the
* global state (FrameTrail.changeState('unsavedChanges', false)) and call the callback.
*
* Note: The second parameter is optional and should not be needed because the user
* should already be logged in at this point (cancelCallback means, the user canceled the login).
*
* @method save
* @param {Function} callback
* @param {Function} callbackCancel
*/
function save(callback, callbackCancel) {
var saveRequests = [],
callbackReturns = [],
databaseCallback = function(result) {
callbackReturns.push(result);
if(callbackReturns.length === saveRequests.length){
saveFinished();
}
};
FrameTrail.module('UserManagement').ensureAuthenticated(
function(){
FrameTrail.module('InterfaceModal').showStatusMessage('Saving...');
if (unsavedOverlays) saveRequests.push(function(){
FrameTrail.module('Database').saveOverlays(databaseCallback);
});
if (unsavedVideolinks) saveRequests.push(function(){
FrameTrail.module('Database').saveLinks(databaseCallback);
});
if (unsavedAnnotations) saveRequests.push(function(){
FrameTrail.module('Database').saveAnnotations(databaseCallback);
});
for (var i in saveRequests) {
saveRequests[i].call();
}
},
function(){
if (callbackCancel) {
callbackCancel.call();
}
}
);
function saveFinished() {
for (var result in callbackReturns) {
if (result.failed) {
// to do: detailed error reporting to the user
FrameTrail.module('InterfaceModal').showErrorMessage('Error: Could not save data.');
return;
}
}
FrameTrail.module('InterfaceModal').showSuccessMessage('Changes have been saved.');
FrameTrail.module('InterfaceModal').hideMessage(2000);
unsavedOverlays = false;
unsavedVideolinks = false;
unsavedAnnotations = false;
FrameTrail.changeState('unsavedChanges', false)
if (callback) {
callback.call();
}
};
}
/**
* The global state "editMode" can be set to false, to trigger all modules to leave their edit mode.
*
* __However__, this global state should only be altered by me, because I check first if there were any unsaved changes,
* and offer the user the possibility to save them.
*
* @method leaveEditMode
* @param {Boolean} logoutAfterLeaving
*/
function leaveEditMode(logoutAfterLeaving) {
if (FrameTrail.getState('unsavedChanges')){
var confirmDialog = $('<div id="ConfirmSaveChanges" title="Save changes?">'
+ ' <div class="message active">Your changes in the current video will be lost if you don\'t save them.</div>'
+ ' <p>Do you want to save your changes?</p>'
+ '</div>');
confirmDialog.dialog({
resizable: false,
modal: true,
close: function() {
confirmDialog.remove();
},
buttons: {
'Yes': function() {
// TODO: Show saving indicator in dialog
save(function(){
FrameTrail.changeState('editMode', false);
confirmDialog.dialog('close');
if (logoutAfterLeaving) {
FrameTrail.module('UserManagement').logout();
}
window.location.reload();
if (logoutAfterLeaving) {
FrameTrail.module('UserManagement').logout();
}
});
},
'No, discard': function() {
FrameTrail.changeState('unsavedChanges', false);
confirmDialog.dialog('close');
if (logoutAfterLeaving) {
FrameTrail.module('UserManagement').logout();
}
window.location.reload();
},
Cancel: function() {
confirmDialog.dialog('close');
}
}
});
} else {
FrameTrail.changeState('editMode', false);
if (logoutAfterLeaving) {
FrameTrail.module('UserManagement').logout();
}
}
}
/**
* Reset & Update Hypervideo Data during runtime
*
* @method updateHypervideo
* @param {String} newHypervideoID
* @param {Boolean} restartEditMode
* @param {Boolean} update
*/
function updateHypervideo(newHypervideoID, restartEditMode, update) {
FrameTrail.module('InterfaceModal').showStatusMessage('Loading ...');
if ( FrameTrail.module('HypervideoController') ) {
FrameTrail.module('HypervideoController').pause();
FrameTrail.module('HypervideoController').clearIntervals();
}
//TODO: Implement proper destroy method
$('#MainContainer #ViewVideo').remove();
FrameTrail.module('RouteNavigation').hypervideoID = newHypervideoID;
FrameTrail.module('Database').updateHypervideoData(function() {
FrameTrail.initModule('ViewVideo');
FrameTrail.initModule('HypervideoModel');
FrameTrail.initModule('HypervideoController');
FrameTrail.module('HypervideoModel').initModel(function(){
FrameTrail.module('ViewVideo').create();
FrameTrail.module('HypervideoController').initController(
function(){
FrameTrail.changeState('viewMode', 'video');
if (restartEditMode) {
FrameTrail.changeState('editMode', 'preview');
}
FrameTrail.module('InterfaceModal').hideMessage(600);
},
function(errorMsg){
FrameTrail.module('InterfaceModal').showErrorMessage(errorMsg);
},
update
);
}, function(errorMsg) {
FrameTrail.module('InterfaceModal').showErrorMessage(errorMsg);
});
}, function() {
console.log('FAIL');
});
}
/**
* YET TO IMPLEMENT
*
* Data exporting can be achieved in various ways.
*
* @method exportIt
*/
function exportIt() {
alert('The Export-Feature is currently being implemented. When finished, it will give you a handy ZIP file which includes a standalone version of your Hypervideo / entire Project.');
}
return {
/**
* Wether the current hypervideo has a playable html5 video source file,
* or (otherwise) only has a duration (then we are in "Null Player" mode).
* @attribute hasHTML5Video
* @type Boolean
* @readOnly
*/
get hasHTML5Video() { return hasHTML5Video },
/**
* I contain a map to the .mp4 and .webm source's filenames.
* @attribute sourceFiles
* @readOnly
* @type {}
*/
get sourceFiles() { return sourceFiles },
/**
* The hypervideo's creator name
* @type String
* @attribute creator
* @readOnly
*/
get creator() { return creator },
/**
* The ID of the hypervideo's creator
* @type String
* @attribute creatorId
* @readOnly
*/
get creatorId() { return creatorId },
/**
* The hypervideo's creation date
* @type Number
* @attribute created
* @readOnly
*/
get created() { return created },
/**
* The hypervideo's date of latest change
* @type Number
* @attribute lastchanged
* @readOnly
*/
get lastchanged() { return lastchanged },
/**
* Whether the hypervideo is hidden in overview mode.
* @type Boolean
* @attribute hidden
* @readOnly
*/
get hidden() { return hidden },
/**
* Get or set the Array of subtitle files (if defined)
* @attribute subtitleFiles
* @param {Array} files
*/
get subtitleFiles() { return subtitleFiles },
set subtitleFiles(files) { return subtitleFiles = files },
/**
* The Array of subtitles (fetched via {{#crossLink "HypervideoModel/getSubtitles:method"}}getSubtitles(){{/crossLinks}}).
* @attribute subtitles
* @readOnly
*/
get subtitles() { return getSubtitles() },
/**
* Get or set the subtitle language
* @type String
* @attribute lang
* @param {String} lang
*/
get selectedLang() { return selectedLang },
set selectedLang(lang) { return selectedLang = lang },
/**
* The overlays of the hypervideo
* @type Array of Overlay
* @attribute overlays
* @readOnly
*/
get overlays() { return overlays },
/**
* The videolinks of the hypervideo (fetched via {{#crossLink "HypervideoModel/getVideolinks:method"}}getVideolinks(){{/crossLinks}}).
* @type Array of Videolink
* @attribute videolinks
* @readOnly
*/
get videolinks() { return getVideolinks() },
/**
* The annotation sets of the hypervideo (fetched via {{#crossLink "HypervideoModel/getAnnotationSets:method"}}getAnnotationSets(){{/crossLinks}}).
* @type Array of { id: String, name: String }
* @attribute annotationSets
* @readOnly
*/
get annotationSets() { return getAnnotationSets() },
/**
* The currently selected annotations of the hypervideo (fetched via {{#crossLink "HypervideoModel/getAnnotations:method"}}getAnnotations(){{/crossLinks}}).
* @type Array of Annotation
* @attribute annotations
* @readOnly
*/
get annotations() { return getAnnotations() },
/**
* All annotations sets of the hypervideo in a map of userIDs to their respective annotation set.
* @type Object of Array of Annotation
* @attribute annotationAllSets
*/
get annotationAllSets() { return annotationSets },
/**
* Get or set the hypervideo name
* @type String
* @attribute hypervideoName
* @param {String} aString
*/
get hypervideoName() { return hypervideoName },
set hypervideoName(aString) { return hypervideoName = aString },
/**
* Get or set the hypervideo descritption
* @type String
* @attribute description
* @param {String} aString
*/
get description() { return description },
set description(aString) { return description = aString },
/**
* The currently selected userID, to decide which annotations should be displayed (setting this attribute is done via {{#crossLink "HypervideoModel/selectAnnotationSet:method"}}selectAnnotationSet(){{/crossLinks}}).
* @type Array of Annotation
* @attribute annotationSet
* @param {} anID
*/
set annotationSet(anID) { return selectAnnotationSet(anID) },
get annotationSet() { return selectedAnnotationSet },
/**
* The hypervideo's duration.
*
* This attribute must not be changed after the init process.
* It is either set to the duration of the "null video" ({{#crossLink "HypervideoModel/initModel:method"}}HypervideoModel/initModel(){{/crossLinks}}) or
* or after the video source file's meta data has loaded ({{#crossLink "HypervideoController/initController:method"}}HypervideoController/initController(){{/crossLinks}}).
*
* @attribute duration
* @param {} aNumber
*/
set duration(aNumber) { return duration = aNumber },
get duration() { return duration },
initModel: initModel,
removeOverlay: removeOverlay,
newOverlay: newOverlay,
removeVideolink: removeVideolink,
newVideolink: newVideolink,
removeAnnotation: removeAnnotation,
newAnnotation: newAnnotation,
// Exception: this is exported to be able to update the subtitles on the fly
initModelOfSubtitles: initModelOfSubtitles,
newUnsavedChange: newUnsavedChange,
save: save,
leaveEditMode: leaveEditMode,
updateHypervideo: updateHypervideo,
exportIt: exportIt
}
});