videoWindow.js
Go to the documentation of this file.
1 /*
2  *=BEGIN SONGBIRD GPL
3  *
4  * This file is part of the Songbird web player.
5  *
6  * Copyright(c) 2005-2009 POTI, Inc.
7  * http://www.songbirdnest.com
8  *
9  * This file may be licensed under the terms of of the
10  * GNU General Public License Version 2 (the ``GPL'').
11  *
12  * Software distributed under the License is distributed
13  * on an ``AS IS'' basis, WITHOUT WARRANTY OF ANY KIND, either
14  * express or implied. See the GPL for the specific language
15  * governing rights and limitations.
16  *
17  * You should have received a copy of the GPL along with this
18  * program. If not, go to http://www.gnu.org/licenses/gpl.html
19  * or write to the Free Software Foundation, Inc.,
20  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21  *
22  *=END SONGBIRD GPL
23  */
24 
31 var Cc = Components.classes;
32 var Ci = Components.interfaces;
33 var Cr = Components.results;
34 var Cu = Components.utils;
35 
36 Cu.import("resource://app/jsmodules/DebugUtils.jsm");
37 Cu.import("resource://app/jsmodules/DOMUtils.jsm");
38 Cu.import("resource://app/jsmodules/SBDataRemoteUtils.jsm");
39 Cu.import("resource://app/jsmodules/SBUtils.jsm");
40 
43  // Debugging aids
45  TRACE: DebugUtils.generateLogFunction("sbVideoWindow", 5),
46  LOG: DebugUtils.generateLogFunction("sbVideoWindow", 3),
47 
49  // Internal data
51 
52  _mediacoreManager: null,
53  _shouldDismissSelf: false,
54  _playbackStopped: false,
55  _ssp: null,
56 
57  _actualSizeDataRemote: null,
58  _lastActualSize: null,
59  _windowNeedsResize: false,
60  _windowNeedsFocus: false,
61 
62  _contextMenu: null,
63  _contextMenuListener: null,
64 
65  _keydownListener: null,
66 
67  _ignoreResize: false,
68  _resizeListener: null,
69  _showing: false,
70 
71  _videoBox: null,
72  _videoElement: null,
73 
74  _videoFullscreenDataRemote: null,
75 
76  _osdService: null,
77 
78  _platform: null,
79 
81  // Getters/Setters
83 
84  get ACTUAL_SIZE_DR_KEY() {
85  const dataRemoteKey = "videowindow.actualsize";
86  return dataRemoteKey;
87  },
88 
89  get VIDEO_FULLSCREEN_DR_KEY() {
90  const dataRemoteKey = "video.fullscreen";
91  return dataRemoteKey;
92  },
93 
94  get TRACK_TITLE_DR_KEY() {
95  const dataRemoteKey = "metadata.title";
96  return dataRemoteKey;
97  },
98 
100  // sbIMediacoreEventListener
102 
103  onMediacoreEvent: function vwc_onMediacoreEvent(aEvent) {
104  switch(aEvent.type) {
105  case Ci.sbIMediacoreEvent.BEFORE_TRACK_CHANGE: {
106  this._handleBeforeTrackChange(aEvent);
107  }
108  break;
109 
110  case Ci.sbIMediacoreEvent.TRACK_CHANGE: {
111  this._handleTrackChange(aEvent);
112  }
113  break;
114 
115  case Ci.sbIMediacoreEvent.SEQUENCE_END: {
116  this._handleSequenceEnd(aEvent);
117  }
118  break;
119 
120  case Ci.sbIMediacoreEvent.VIDEO_SIZE_CHANGED: {
121  this._handleVideoSizeChanged(aEvent);
122  }
123  break;
124 
125  case Ci.sbIMediacoreEvent.EXPLICIT_TRACK_CHANGE: {
126  this._handleExplicitTrackChange(aEvent);
127  }
128  break;
129  }
130  },
131 
133  // nsIObserver
135 
136  observe: function vwc_observe(aSubject, aTopic, aData) {
137  if(aTopic == this.ACTUAL_SIZE_DR_KEY &&
138  this._actualSizeDataRemote.boolValue == true &&
139  this._videoBox) {
140  this._resizeFromVideoBox(this._videoBox);
141  }
142  else if(aTopic == this.VIDEO_FULLSCREEN_DR_KEY &&
143  !this._ignoreResize) {
144  this._onFullScreen();
145  }
146  else if(aTopic == this.TRACK_TITLE_DR_KEY) {
147  // Set the document title to the current track name
148  window.document.title = aData;
149  }
150  },
151 
153  // Init/Shutdown
155 
156  _initialize: function vwc__initialize() {
157  this._mediacoreManager =
158  Cc["@songbirdnest.com/Songbird/Mediacore/Manager;1"]
159  .getService(Ci.sbIMediacoreManager);
160 
161  // Inform the OSD control service that a video window is opening.
162  this._osdService = Cc["@songbirdnest.com/mediacore/osd-control-service;1"]
163  .getService(Ci.sbIOSDControlService);
164  this._osdService.onVideoWindowOpened(window);
165 
166  this._mediacoreManager.addListener(this);
167 
168  try {
169  this._ssp = Cc["@songbirdnest.com/Songbird/ScreenSaverSuppressor;1"]
170  .getService(Ci.sbIScreenSaverSuppressor);
171  } catch(e) {
172  // No SSP on this platform.
173  }
174 
175  // If for some reason the user doesn't have the right to suppress
176  // the screen saver 'suppress' may fail but we shouldn't fail
177  // to initialize the video window because of this.
178  try {
179  if (this._ssp)
180  this._ssp.suppress(true);
181  }
182  catch(e) {
183  Cu.reportError(e);
184  }
185 
186 
187  this._actualSizeDataRemote = SBNewDataRemote(this.ACTUAL_SIZE_DR_KEY);
188 
189  // Catch un-initialized actual size data remote value and default
190  // to true in the case where it has no value yet.
191  if(this._actualSizeDataRemote.stringValue == null ||
192  this._actualSizeDataRemote.stringValue == "") {
193  this._actualSizeDataRemote.boolValue = true;
194  }
195  this._lastActualSize = this._actualSizeDataRemote.boolValue;
196 
197  this._actualSizeDataRemote.bindObserver(this);
198 
199  this._videoFullscreenDataRemote =
200  SBNewDataRemote(this.VIDEO_FULLSCREEN_DR_KEY);
201  this._videoFullscreenDataRemote.boolValue = false;
202  this._videoFullscreenDataRemote.bindObserver(this);
203 
204  // Watch the track name, which we'll use to set the window title:
205  this._titleDataRemote = SB_NewDataRemote(this.TRACK_TITLE_DR_KEY, null);
206  this._titleDataRemote.bindObserver(this, false);
207 
208 
209  // We need to ignore the first resize.
210  this._ignoreResize = true;
211 
212  var self = this;
213 
214  // Resize hook:
215  this._resizeListener = function(aEvent) {
216  self._onResize(aEvent);
217  };
218  window.addEventListener("resize", this._resizeListener, false);
219 
220  // Mouse move hook:
221  this._mouseListener = function(aEvent) {
222  self._onMouseMoved(aEvent);
223  };
224  window.addEventListener("mousemove", this._mouseListener, false);
225 
226  // Context menu hook:
227  this._contextMenuListener = function(aEvent) {
228  return self._onContextMenu(aEvent);
229  };
230  window.addEventListener("contextmenu", this._contextMenuListener, false);
231 
232  // Keydown hook:
233  this._keydownListener = function(aEvent) {
234  return self._onKeyDown(aEvent);
235  };
236  window.addEventListener("keypress", this._keydownListener, false);
237 
238  this._contextMenu = document.getElementById("video-context-menu");
239  this._videoElement = document.getElementById("video-box");
240 
241  // Stash current platform
242  this._platform = getPlatformString();
243  },
244 
245  _shutdown: function vwc__shutdown() {
246  // Stop playback when the window closes unless it is closing because
247  // playback stopped.
248  if (!this._playbackStopped)
249  this._mediacoreManager.sequencer.stop();
250 
251  window.removeEventListener("resize", this._resizeListener, false);
252  this._resizeListener = null;
253 
254  window.removeEventListener("contextmenu", this._contextMenuListener, false);
255  this._contextMenuListener = null;
256 
257  window.removeEventListener("keypress", this._keydownListener, false);
258  this._keydownListener = null;
259 
260  this._titleDataRemote.unbind();
261  this._actualSizeDataRemote.unbind();
262  this._videoFullscreenDataRemote.unbind();
263 
264  this._titleDataRemote = null;
265  this._actualSizeDataRemote = null;
266  this._videoFullscreenDataRemote = null;
267 
268  this._osdService.onVideoWindowWillClose();
269  this._osdService = null;
270 
271  this._mediacoreManager.removeListener(this);
272  this._mediacoreManager = null;
273  this._ssp = null;
274  this._actualSizeDataRemote = null;
275  this._videoBox = null;
276  this._videoElement = null;
277  },
278 
280  // Actual Size Resizing Methods
282  _resizeFromVideoBox: function vwc__resizeFromVideoBox(aVideoBox) {
283 
284  var actualHeight = aVideoBox.height;
285  var actualWidth = aVideoBox.width * aVideoBox.parNumerator /
286  aVideoBox.parDenominator;
287 
288  this._resizeFromWidthAndHeight(actualWidth,
289  actualHeight,
290  aVideoBox.parNumerator,
291  aVideoBox.parDenominator);
292  },
293 
294  _resizeFromWidthAndHeight: function vwc__resizeFromWidthAndHeight(aWidth,
295  aHeight,
296  aPARNum,
297  aPARDen) {
298  function log(str) {
299  videoWindowController.TRACE("_resizeFromWidthAndHeight: " + str);
300  }
301 
302  log("Width: " + aWidth);
303  log("Height: " + aHeight);
304 
305  var screen = window.screen;
306  var availHeight = screen.availHeight;
307  var availWidth = screen.availWidth;
308 
309  log("Screen: " + screen);
310  log("Available Width: " + availWidth);
311  log("Available Height: " + availHeight);
312  log("Window X: " + window.screenX);
313  log("Window Y: " + window.screenY);
314  log("Window width: " + window.outerWidth);
315  log("Window height: " + window.outerHeight);
316 
317  var fullWidth = false;
318  var deltaWidth = 0;
319 
320  var fullHeight = false;
321  var deltaHeight = 0;
322 
323  var boxObject = this._videoElement.boxObject;
324  var orient = (aWidth < aHeight) ? "portrait" : "landscape";
325 
326  log("Video Orientation: " + orient);
327 
328  log("Video Element Width: " + boxObject.width);
329  log("Video Element Height: " + boxObject.height);
330 
331  var decorationsWidth = window.outerWidth - boxObject.width;
332  var decorationsHeight = window.outerHeight - boxObject.height;
333 
334  log("Decorations Width: " + decorationsWidth);
335  log("Decorations Height: " + decorationsHeight);
336 
337  // Doesn't fit width wise, get the ideal width.
338  if(aWidth > boxObject.width) {
339  let delta = aWidth - boxObject.width;
340 
341  log("Initial Delta Width: " + delta);
342 
343  // We're making the window bigger. Make sure it fits on the screen
344  // width wise.
345  let available = availWidth - window.outerWidth - window.screenX;
346 
347  log("Current Available Width: " + available);
348 
349  if(available > delta) {
350  // Looks like we have room.
351  deltaWidth = delta;
352  fullWidth = true;
353  }
354  else {
355  // Not enough room for the size we want, just use the available size.
356  deltaWidth = available;
357  }
358  }
359  else {
360  // No need to cap anything in this case since the
361  // window is already bigger.
362  deltaWidth = aWidth - boxObject.width;
363  fullWidth = true;
364  }
365 
366 
367  // Doesn't fit height wise, get the ideal height.
368  if(aHeight > boxObject.height) {
369  let delta = aHeight - boxObject.height;
370 
371  log("Initial Delta Height: " + delta);
372 
373  // We're making the window bigger. Make sure it fits on the screen
374  // height wise.
375  let available = availHeight - window.outerHeight - window.screenY;
376 
377  log("Current Available Height: " + available);
378 
379  if(available >= delta) {
380  // Looks like we have room.
381  deltaHeight = delta;
382  fullHeight = true;
383  }
384  else {
385  // Not enough room for the size we want, just use the available size.
386  deltaHeight = available;
387  }
388  }
389  else {
390  // Negative delta, we're resizing smaller.
391  deltaHeight = aHeight - boxObject.height;
392  fullHeight = true;
393  }
394 
395  log("Final Delta Width: " + deltaWidth);
396  log("Final Delta Height: " + deltaHeight);
397  log("Full Width: " + fullWidth);
398  log("Full Height: " + fullHeight);
399 
400  // Try and resize in a somewhat proportional manner.
401  if(orient == "landscape" && deltaHeight > deltaWidth && !fullWidth) {
402  let mul = aPARDen / aPARNum;
403  if((aPARDen / aPARNum) == 1) {
404  mul = aHeight / aWidth;
405  }
406  deltaHeight = Math.round(deltaWidth * mul);
407  }
408  else if(orient == "portrait" && deltaWidth > deltaHeight && !fullHeight) {
409  let mul = aPARNum / aPARDen;
410  if((aPARNum / aPARDen) == 1) {
411  mul = aWidth / aHeight;
412  }
413  deltaWidth = Math.round(deltaHeight * mul);
414  }
415 
416  log("Final Delta Width (With aspect ratio compensation): " + deltaWidth);
417  log("Final Delta Height (With aspect ratio compensation): " + deltaHeight);
418 
419  if (deltaWidth || deltaHeight)
420  {
421  // We have to ignore this resize so that we don't disable actual size.
422  // This doesn't actually prevent the window from getting resized, it just
423  // prevents the actual size data remote from being set to false.
424  this._ignoreResize = true;
425 
426  // Resize it!
427  window.resizeBy(deltaWidth, deltaHeight);
428  }
429 
430  log("New Video Element Width: " + boxObject.width);
431  log("New Video Element Height: " + boxObject.height);
432  },
433 
434  _moveToCenter: function vwc__moveToCenter() {
435  var posX = (window.screen.availWidth - window.outerWidth) / 2;
436  var posY = (window.screen.availHeight - window.outerHeight) / 2;
437 
438  window.moveTo(posX, posY);
439  },
440 
441  _onFullScreen: function vwc__onFullScreen() {
442  var full = this._videoFullscreenDataRemote.boolValue
443  if (window.fullScreen != full) {
444  if (!full)
445  {
446  // We have to ignore this resize so that we don't disable actual size.
447  // This doesn't actually prevent the window from getting resized, it just
448  // prevents the actual size data remote from being set to false.
449  this._ignoreResize = true;
450  }
451 
452  window.fullScreen = full;
453  if (full) {
454  document.documentElement.setAttribute("fullscreen", full);
455  }
456  else {
457  document.documentElement.removeAttribute("fullscreen");
458  }
459  this._osdService.onVideoWindowFullscreenChanged(full);
460 
461  if (full) {
462  this._lastActualSize = this._actualSizeDataRemote.boolValue;
463  this._actualSizeDataRemote.boolValue = false;
464  }
465  else {
466  this._actualSizeDataRemote.boolValue = this._lastActualSize;
467  }
468 
469  window.focus();
470  }
471  },
472 
473  _setFullScreen: function vwc__setFullScreen(aFullScreen) {
474  this._videoFullscreenDataRemote.boolValue = aFullScreen;
475  },
476 
477  _toggleFullScreen: function vwc__toggleFullScreen() {
478  this._setFullScreen(!this._videoFullscreenDataRemote.boolValue);
479  },
480 
481  _setActualSize: function vwc__setActualSize() {
482  this._setFullScreen(false);
483  this._actualSizeDataRemote.boolValue = true;
484  },
485 
487  // Mediacore Event Handling
489 
490  _handleBeforeTrackChange: function vwc__handleBeforeTrackChange(aEvent) {
491  var mediaItem = aEvent.data.QueryInterface(Ci.sbIMediaItem);
492 
493  // If the next item is not video, we will dismiss
494  // the window on track change.
495  if(mediaItem.contentType != "video") {
496  this._dismissSelf();
497  }
498 
499  // Clear cached video box.
500  this._videoBox = null;
501  },
502 
503  _handleTrackChange: function vwc__handleTrackChange(aEvent) {
504  if(this._shouldDismiss) {
505  this._dismissSelf();
506  this._shouldDismiss = false;
507  }
508 
509  if(this._windowNeedsFocus) {
510  window.focus();
511  this._windowNeedsFocus = false;
512  }
513  },
514 
515  _handleSequenceEnd: function vwc__handleSequenceEnd(aEvent) {
516  this._dismissSelf();
517  },
518 
519  _handleVideoSizeChanged: function vwc__handleVideoSizeChanged(aEvent) {
520  if(!(aEvent.data instanceof Ci.sbIVideoBox))
521  return;
522 
523  var videoBox = aEvent.data;
524 
525  // If actual size is enabled and we are not in fullscreen we can
526  // go ahead and 'actual size' the video.
527  if(this._actualSizeDataRemote.boolValue == true && !window.fullScreen) {
528  // Size it!
529  this._resizeFromVideoBox(videoBox);
530 
531  // Window needs to be centered.
532  if(this._needsMove) {
533  this._moveToCenter();
534  this._needsMove = false;
535  }
536  }
537 
538  // We also probably always want to save the last one so that if the user
539  // turns on actual size, we can resize to the right thing.
540  this._videoBox = videoBox;
541  },
542 
543  _handleExplicitTrackChange: function vwc__handleExplicitTrackChange(aEvent) {
544  this._windowNeedsFocus = true;
545  },
546 
548  // UI Event Handling
550 
551  _onContextMenu: function vwc__onContextMenu(aEvent) {
552  if(this._contextMenu.state == "open")
553  this._contextMenu.hidePopup();
554 
555  this._setChecked(document.getElementById("actualsize"),
556  this._actualSizeDataRemote.boolValue);
557  this._setChecked(document.getElementById("fullscreen"),
558  this._videoFullscreenDataRemote.boolValue);
559 
560  this._contextMenu.openPopupAtScreen(aEvent.screenX, aEvent.screenY, true);
561 
562  return true;
563  },
564 
565  _setChecked: function(node, state)
566  {
567  if (state)
568  node.setAttribute("checked", "true");
569  else
570  node.removeAttribute("checked");
571  },
572 
573  _onResize: function vwc__onResize(aEvent) {
574  // Inform the OSD service that we are resizing.
575  this._osdService.onVideoWindowResized();
576 
577  // Any resize by the user disables actual size except when the resize event
578  // is sent because the window is being shown for the first time, or we are
579  // attempting to size it using the sizing hint.
580  if(this._ignoreResize) {
581  this._ignoreResize = false;
582 
583  // First time window is being shown, it will be moved to the center
584  // of the screen.
585  if(!this._showing) {
586  this._needsMove = true;
587  this._showing = true;
588  }
589 
590  return;
591  }
592 
593  // Actual size is now disabled.
594  this._actualSizeDataRemote.boolValue = false;
595  },
596 
597  _onMouseMoved: function vwc__onMouseMoved(aEvent) {
598  // Ignore mouse events while context menu is open.
599  if (this._contextMenu.state != "closed")
600  return;
601 
602  // Ignore any events outside the video element. Note that mouse move events
603  // over the video itself are dispatched to the document, these are allowed.
604  var target = aEvent.target;
605  if (target instanceof XULElement && target != this._videoElement)
606  return;
607 
608  this._osdService.showOSDControls(Ci.sbIOSDControlService.TRANSITION_NONE);
609  },
610 
611  _onKeyDown: function vwc__onKeyDown(aEvent) {
612  this._osdService.showOSDControls(Ci.sbIOSDControlService.TRANSITION_NONE);
613 
614  var keyCode = aEvent.keyCode;
615 
616  // Fallback to charCode if keyCode is not available.
617  if(!keyCode) {
618  keyCode = aEvent.charCode;
619  }
620 
621  // Get out of fullscreen
622  if(keyCode == KeyEvent.DOM_VK_ESCAPE) {
623  this._setFullScreen(false);
624  return;
625  }
626 
627  // Pause/Play
628  if(keyCode == KeyEvent.DOM_VK_SPACE) {
629  let state = this._mediacoreManager.status.state;
630  if((state == Ci.sbIMediacoreStatus.STATUS_BUFFERING) ||
631  (state == Ci.sbIMediacoreStatus.STATUS_PLAYING)) {
632  this._mediacoreManager.playbackControl.pause();
633  }
634  else if(this._mediacoreManager.primaryCore) {
635  this._mediacoreManager.playbackControl.play();
636  }
637  return;
638  }
639 
640  // Next Item
641  if(aEvent.ctrlKey && keyCode == KeyEvent.DOM_VK_RIGHT) {
642  this._mediacoreManager.sequencer.next();
643  return;
644  }
645 
646  // Previous Item
647  if(aEvent.ctrlKey && keyCode == KeyEvent.DOM_VK_LEFT) {
648  this._mediacoreManager.sequencer.previous();
649  }
650 
651  // Volume Up
652  if(!aEvent.altKey && aEvent.ctrlKey && keyCode == KeyEvent.DOM_VK_UP) {
653  let vol = this._mediacoreManager.volumeControl.volume;
654  vol += 0.03;
655 
656  // Clamp.
657  if(vol > 1) {
658  vol = 1;
659  }
660 
661  this._mediacoreManager.volumeControl.volume = vol;
662 
663  return;
664  }
665 
666  // Volume Down
667  if(!aEvent.altKey && aEvent.ctrlKey && keyCode == KeyEvent.DOM_VK_DOWN) {
668  let vol = this._mediacoreManager.volumeControl.volume;
669  vol -= 0.03;
670 
671  // Clamp.
672  if(vol < 0) {
673  vol = 0;
674  }
675 
676  this._mediacoreManager.volumeControl.volume = vol;
677 
678  return;
679  }
680 
681  // Toggle Mute
682  if(aEvent.ctrlKey && aEvent.altKey &&
683  (keyCode == KeyEvent.DOM_VK_UP ||
684  keyCode == KeyEvent.DOM_VK_DOWN)) {
685  let mute = this._mediacoreManager.volumeControl.mute;
686  this._mediacoreManager.volumeControl.mute = !mute;
687 
688  return;
689  }
690 
691  // Handle ALT + F4 and CTRL + W key shortcut if we are on windows
692  if(this._platform == "Windows_NT") {
693  if((aEvent.altKey && (keyCode == KeyEvent.DOM_VK_F4)) ||
694  (aEvent.ctrlKey && (keyCode == KeyEvent.DOM_VK_W))) {
695  this._mediacoreManager.sequencer.stop();
696  this._dismissSelf();
697  return;
698  }
699  }
700  },
701 
702  _dismissSelf: function vwc__dismissSelf() {
703  if (this._ssp)
704  this._ssp.suppress(false);
705 
706  this._playbackStopped = true;
707  setTimeout(function() { window.close(); }, 0);
708  }
709 };
#define LOG(args)
function getPlatformString()
Get the name of the platform we are running on.
restoreDimensions aWidth
function log(s)
var Cu
Definition: videoWindow.js:34
let window
const SB_NewDataRemote
restoreDimensions aHeight
var Cc
Definition: videoWindow.js:31
var Ci
Definition: videoWindow.js:32
function SBNewDataRemote(aKey, aRoot)
Create a new data remote object.
function TRACE(s)
aWindow setTimeout(function(){_this.restoreHistory(aWindow, aTabs, aTabData, aIdMap);}, 0)
return null
Definition: FeedWriter.js:1143
let node
var Cr
Definition: videoWindow.js:33
var videoWindowController
Definition: videoWindow.js:41
_updateTextAndScrollDataForFrame aData
sbDeviceFirmwareAutoCheckForUpdate prototype observe