Remoto - VFS: video.js Source File
Remoto - VFS
video.js
Go to the documentation of this file.
1 
2 define( [
3  'remoto!stdlib:js/panes/pane.js',
4  'remoto!stdlib:js/include/objectRegistry.js',
5  'remoto!stdlib:js/include/utils.js',
6  ],
7  function(pane,objectRegistry,utils)
8  {
9  'use strict';
10 
34  var attrs = ["src","controls","loop","muted","poster","play","volume","restart","chapter","feedback"];
35  var boolTags = ["controls","loop","muted"];
36  var chapterEpsilon = .5;
37 
48  videoPane.prototype = new pane;
49  function videoPane(layout)
50  {
51  pane.call(this,layout);
52 
53  //this._type = 'video';
54 
55  this._video = null;
56  this._videoE = null;
57 
58  this._src = null;
59  this._controls = null;
60  this._loop = null;
61  this._muted = null;
62  this._poster = null;
63  this._volume = 1;
64  this._chapter = null;
65  this._chapterTrack = null;
66 
67  this._play = false;
68  this._activityPulse = null; //a setInterval timer used to notify a containing paneTransitioner that it should not fall asleep
69  this._firstLoad = false;
70  this._feedback = true;
71  this._playbutton = null;
72  }
73 
83  videoPane.prototype.createHTML = function()
84  {
85  if (this._html) return this._html;
86 
87  pane.prototype.createHTML.call(this);
88 
89  this._video = $("<video>").appendTo(this._content);
90  this._video.attr("id", "video_"+utils.uuid() );
91  this._videoE = this._video[0];
92  this._video.css({
93  width:"100%",
94  height:"100%",
95  "background-color":"black",
96  });
97  //http://www.w3schools.com/tags/ref_eventattributes.asp
98  this._video.bind({
99  // 'load': function() { console.log("video load"); },
100  'canplay': this.videoLoaded.bind(this),
101  // 'loadeddata': function() { console.log("loadedddata"); },
102  // 'ended': function() { console.log("ended"); },
103  'error': function() { console.log("video error"); },
104  // 'error': this.videoLoadError.bind(this),
105  'play': this.playChange.bind(this),
106  // 'playing': function() { console.log("playing"); },
107  'pause': this.playChange.bind(this),
108  // 'seeked': function() { console.log("seeked"); },
109  // 'stalled': this.videoLoaded.bind(this),
110  // 'stalled': this.videoLoadError.bind(this),
111  'ended': this.videoEnded.bind(this),
112  'volumechange': this.volumeChange.bind(this),
113  });
114 
115  // this._content.trigger("paneLoadStart"); //must wait until after _html has been attached!
116 
117  return this._html;
118  }
119 
128  videoPane.prototype.detach = function()
129  {
130  var detachPlaystate = !this._videoE.paused;
131  //console.log("DETACH: "+detachPlaystate);
132 
133  var inserted = (function(e) { //create a named bound function so we can unbind it exactly, and keep any other listeners listening
134  if (jQuery.contains(document.documentElement, this._videoE)) //http://jsperf.com/jquery-element-in-dom/2
135  this.play = detachPlaystate;
136  $('body').unbind('DOMNodeInserted',inserted);
137  }).bind(this);
138 
139  $('body').bind('DOMNodeInserted',inserted);
140  }
141 
150  videoPane.prototype.attached = function()
151  {
152  //console.log("ATTACH!");
153  }
154 
166  videoPane.prototype.applySubscription = function(data,metadata)
167  {
168  //console.log("videoPane Apply Subscription!");
169  //console.log(arguments);
170 
171  if (this._subscribed)
172  return;
173 
174  this._subscribed = true;
175 
176  //this._nodeMenu = metadata.nodeMenu;
177 
178  if (data.base)
179  { this._objectLoader = new objectRegistry.objectLoader(metadata.nodeMenu);
180  return this._objectLoader.fetchDefinition( data.base, data, this.applyDiff.bind(this) );
181  }
182  else
183  return this.applyDiff(data);
184  }
185 
196  videoPane.prototype.applyDiff = function(diff,user)
197  {
198  //console.log("videoPane applyDiff");
199  //console.log(arguments);
200 
201  if ("attributes" in diff)
202  diff = diff.attributes;
203 
204  for (var a in diff)
205  {
206  if ( attrs.indexOf(a) > -1)
207  {
208  switch(a)
209  { case "play": this.play = diff.play.value; break;
210  case "volume": this.volume = diff.volume.value; break;
211  case "muted": this.muted = diff.muted.value; break;
212  case "loop": this.loop = diff.loop.value; break;
213  case "restart": this.currentTime = 0; break;
214  case "feedback":this._feedback = !!diff.feedback.value; break;
215 
216  case "chapter": if ('options' in diff.chapter)
217  this.buildChapters(diff.chapter.options.options,diff.chapter.value);
218  this.chapter = diff.chapter.value;
219  break;
220 
221  case "src": this._src = diff.src.value;
222  this._video.empty();
223  for (var mime in this._src)
224  $("<source>").attr({type:mime,src:this._src[mime]}).appendTo(this._video);
225  this._content.trigger("paneLoadStart");
226  break;
227 
228  default: this["_"+a] = diff[a].value;
229  if (boolTags.indexOf(a) > -1) //is bool!
230  { if (diff[a].value) this._video.attr(a,true);
231  else this._video.removeAttr(a);
232  }
233  else
234  this._video.attr(a,diff[a].value);
235  break;
236  }
237  }
238  //else
239  // console.warn("bad video attribute: "+a);
240  }
241  }
242 
252  videoPane.prototype.applySettings = function(settings)
253  {
254  //console.log("videoPane settings!");
255  //console.log(arguments);
256  }
257 
267  videoPane.prototype.videoLoaded = function()
268  {
269  //console.log("Loaded!");
270  //console.log(arguments);
271 
272  this.play = this._play;
273 
274  if (!this._firstLoad)
275  { this._content.trigger("paneLoadComplete");
276  this._firstLoad = true;
277  }
278 
279  this._content.find(".videoError").remove();
280 
281  return false;
282  }
283 
292  videoPane.prototype.videoEnded = function()
293  {
294  //http://stackoverflow.com/questions/21540959/html5-video-embed-return-to-poster-at-end-of-play
295 
296  //console.dir(this._videoE);
297  //console.log("ended");
298 
299  //reset the video to the poster frame, if it exists.
300  this._videoE.src = this._videoE.currentSrc;
301 
302  this.chapter = -1;
303 
304  if (!this._loop && this._feedback)
305  {
306  var u = { attributes: { play: { value: false } } };
307  objectRegistry.applyDiff( this._path, u, null, this );
308  this.postUpdate( u );
309  }
310  }
311 
321  videoPane.prototype.videoLoadError = function()
322  {
323  console.error("Video load error!");
324  console.log(arguments);
325 
326  // this._content.append( "<span class='videoError' style='color:red; padding:10px; display:block; position:absolute; z-index: 1000000; top:0px; left:0px;'>Unable to load video url: "+this._src[Object.keys(this._src)[0]]+"</span>" );
327 
328  if (!this._firstLoad)
329  { this._content.trigger("paneLoadComplete");
330  this._firstLoad = true;
331  }
332 
333  return false;
334  }
335 
345  videoPane.prototype.playChange = function(e)
346  {
347  if (this._play !== (!this._videoE.paused))
348  if (this._feedback)
349  {
350  var p = this.play = !this._videoE.paused;
351  var u = { attributes: { play: { value: p } } };
352  objectRegistry.applyDiff( this._path, u, null, this );
353  this.postUpdate( u );
354  }
355  }
356 
366  videoPane.prototype.volumeChange = function(e)
367  {
368  if (this._volume !== this._videoE.volume)
369  {
370  var v = this.volume = this._videoE.volume;
371 
372  var u = { attributes: { volume: { value: v } } };
373 
374  objectRegistry.applyDiff( this._path, u, null, this );
375 
376  this.postUpdate( u );
377  }
378 
379  if (this._muted !== this._videoE.muted)
380  {
381  var m = this.muted = this._videoE.muted;
382 
383  var u = { attributes: { muted: { value: m } } };
384 
385  objectRegistry.applyDiff( this._path, u, null, this );
386 
387  this.postUpdate( u );
388  }
389  }
390 
391 
392 
393 
405  videoPane.prototype.__defineSetter__("play", function(p)
406  {
407  this._play = p;
408  //console.log(p);
409 
410  if (this._play)
411  {
412  let startPlayPromise = this._videoE.play();
413 
414  if (startPlayPromise !== undefined) {
415  startPlayPromise.then(() => {
416  // Start whatever you need to do only after playback
417  // has begun.
418  }).catch(error => {
419  if (error.name === "NotAllowedError" && this._playbutton == null) {
420  if (!this._firstLoad)
421  { this._content.trigger("paneLoadComplete");
422  this._firstLoad = true;
423  }
424 
425  this._playbutton = $("<button>").addClass("playButton").appendTo(this._content);
426  this._playbutton.css({
427  position: "absolute",
428  left: "45vw",
429  top:"45vh",
430  width:"10vw",
431  height:"10vw",
432  border:"none",
433  "background-repeat": "no-repeat",
434  "background-color":"transparent",
435  "background-size":"contain",
436  "background-image": "url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjRkZGRkZGIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNjAgNjAiIHg9IjBweCIgeT0iMHB4Ij48ZGVmcz48Y2xpcFBhdGggaWQ9ImEiPjxyZWN0IHdpZHRoPSI2MCIgaGVpZ2h0PSI2MCI+PC9yZWN0PjwvY2xpcFBhdGg+PC9kZWZzPjxnIGRhdGEtbmFtZT0iY3VzdG9tIOKAkyAyMCIgY2xpcC1wYXRoPSJ1cmwoI2EpIj48cGF0aCBkYXRhLW5hbWU9IlRyYWPDqSA5IiBkPSJNMTcuNzM1LDAsMzUuNDcsMzAuNzg1SDBaIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0NS43ODUgMTIpIHJvdGF0ZSg5MCkiPjwvcGF0aD48L2c+PC9zdmc+)"
437  });
438  var that = this;
439  this._playbutton.click(function(){
440  that._videoE.play();
441  this.remove();
442  that._playbutton = null;
443  });
444  }
445  });
446  }
447 
448  if (!this._activityPulse)
449  { this._activityPulse = setInterval( (function() { this._content.trigger("paneActivity"); }).bind(this), 3000 ); //trigger "paneActivity" every 3 seconds so the transitioner doesn't fall asleep
450  //console.log("started paneActivity");
451  this._content.trigger("paneActivity");
452  }
453  }
454  else
455  { this._videoE.pause();
456  if (this._activityPulse)
457  { clearInterval( this._activityPulse );
458  this._activityPulse = null;
459  //console.log("stopped paneActivity");
460  }
461  }
462 
463  //console.dir(this._videoE);
464  });
465 
472  videoPane.prototype.__defineSetter__("volume", function(v)
473  {
474  v = Math.min( Math.max( v, 0 ), 1 );
475 
476  this._volume = v;
477  this._videoE.volume = v;
478  });
479 
486  videoPane.prototype.__defineSetter__("muted", function(m)
487  {
488  this._muted = m;
489 
490  this._videoE.muted = m;
491 
492  if (m)
493  this._video.attr("muted",true);
494  else
495  this._video.removeAttr("muted");
496  });
497 
504  videoPane.prototype.__defineSetter__("loop", function(l)
505  {
506  this._loop = l;
507  this._videoE.loop = l;
508 
509  if (l)
510  this._video.attr("loop",true);
511  else
512  this._video.removeAttr("loop");
513 
514  });
515 
522  videoPane.prototype.__defineSetter__("currentTime", function(t)
523  {
524  //console.log("set current time: "+t);
525 
526  this._videoE.currentTime = t;
527  });
528 
535  videoPane.prototype.__defineSetter__("chapter", function(c)
536  {
537  if (c)
538  {
539  if (this._chapter != c || Math.abs(this._videoE.currentTime - c) > chapterEpsilon)
540  if ( c < this._videoE.duration)
541  //if (this._chapter != c)
542  {
543  this._chapter = c;
544 
545  //console.trace("set chapter: "+c);
546 
547  //if (this._videoE.paused)
548  if (Math.abs(this._videoE.currentTime - c) > chapterEpsilon)
549  this.currentTime = c;
550 
551  var u = { attributes: { chapter: { value: c } } };
552 
553  objectRegistry.applyDiff( this._path, u, null, this );
554 
555  this.postUpdate( u );
556  }
557  }
558  });
559 
572  videoPane.prototype.buildChapters = function(chapters,chapter)
573  {
574  //http://www.html5rocks.com/en/tutorials/track/basics/
575  //http://www.samdutton.com/track/audioSprites/
576  //add tracks with javascript, and set them.
577 
578  //because video tracks are not evenly supported, we wrap this in a try block
579  try
580  {
581  if (!this._chapterTrack)
582  { this._chapterTrack = this._videoE.addTextTrack("chapters");
583  //this._video.append(this._chapterTrack);
584  //console.dir(this._chapterTrack);
585  var THIS = this;
586  $(this._chapterTrack).bind("cuechange", function(e) {
587 
588  //console.log("cue changed!");
589 
590  if (THIS._chapterTrack.activeCues.length)
591  { var cue = THIS._chapterTrack.activeCues[0];
592  THIS.chapter = cue.startTime;
593  }
594  else
595  THIS.chapter = null;
596  });
597  }
598  else
599  {
600  while (this._chapterTrack.cues.length)
601  this._chapterTrack.removeCue( this._chapterTrack.cues[0] );
602  }
603 
604  for (var i=0;i<chapters.length;i++)
605  {
606  if (chapters[i].value) //the first chapter should be null, which will allow for gaps between chapters.
607  {
608  //console.log("add: "+chapters[i].text);
609  var s = chapters[i].value;
610  var e = i < chapters.length - 1 ? chapters[i+1].value - .001 : 10000000;
611  var cue = new VTTCue( s, e, chapters[i].text );
612  this._chapterTrack.addCue( cue );
613  }
614  }
615 
616  this._chapter = chapter;
617 
618  //console.dir(this._chapterTrack);
619  }
620  catch (e)
621  {
622  console.error("Unable to create chapters... sorry.");
623  console.log(e);
624  }
625  };
626 
635  videoPane.prototype.destroy = function()
636  {
637  if (this._activityPulse)
638  { clearInterval( this._activityPulse );
639  this._activityPulse = null;
640  //console.log("stopped paneActivity");
641  }
642 
643  this._videoE = null;
644  this._video = null;
645  this._playbutton = null;
646 
647  pane.prototype.destroy.call(this);
648  };
649 
650  return videoPane;
651  }
652 );
653 
654 
655 
setter play
a setter DOCME
setter chapter
a setter DOCME
setter value
a setter DOCME
setter volume
a setter DOCME
setter user
a setter DOCME
setter type
a setter DOCME
applyDiff(id, diff, user, except)
setter postUpdate
Assign a callback to the paneFactory class for VFS_node::submit() commands.
Create a pane which will be mounted into a paneManager layout.
destroy()
DOCME.
createHTML()
pane(layout, object)
applyDiff(diff, user)
Utility functions for javascript clients.
uuid()
Generate a universally unique identifier.
text(value, options)
metadata(paths)
The videoPane will create a <video> tag and manage its settings.
applySettings(settings)
videoPane(layout)
videoPane constructor
setter muted
a setter DOCME
setter loop
a setter DOCME
buildChapters(chapters, chapter)
setter currentTime
a setter DOCME
applyDiff(diff, user)
applySubscription(data, metadata)
setter error
Set the error value of this widget.