You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2109 line
75KB

  1. /*
  2. * ShowController.js
  3. * Keynote HTML Player
  4. *
  5. * Responsibility: Tungwei Cheng
  6. * Copyright (c) 2009-2016 Apple Inc. All rights reserved.
  7. */
  8. var kShowControllerState_Stopped = "Stopped";
  9. var kShowControllerState_Starting = "Starting";
  10. var kShowControllerState_DownloadingScript = "DownloadingScipt";
  11. var kShowControllerState_SettingUpScene = "SettingUpScene";
  12. var kShowControllerState_IdleAtFinalState = "IdleAtFinalState";
  13. var kShowControllerState_IdleAtInitialState = "IdleAtInitialState";
  14. var kShowControllerState_WaitingToJump = "WaitingToJump";
  15. var kShowControllerState_ReadyToJump = "ReadyToJump";
  16. var kShowControllerState_WaitingToDisplay = "WaitingToDisplay";
  17. var kShowControllerState_ReadyToDisplay = "ReadyToDisplay";
  18. var kShowControllerState_WaitingToPlay = "WaitingToPlay";
  19. var kShowControllerState_ReadyToPlay = "ReadyToPlay";
  20. var kShowControllerState_Playing = "Playing";
  21. // Events:
  22. // -------
  23. var kKeyDownEvent = "keydown";
  24. var kSlideIndexDidChangeEvent = "ShowController:SlideIndexDidChangeEvent";
  25. var ShowController = Class.create({
  26. initialize: function() {
  27. // extract delegate from url or create a default delegate
  28. this.delegate = extractDelegateFromUrlParameter();
  29. this.delegate.showDidLoad();
  30. this.showUrl = "../";
  31. // These must be created before the OrientationController as they
  32. // subscribe to its event
  33. this.displayManager = new DisplayManager();
  34. this.scriptManager = new ScriptManager(this.showUrl);
  35. this.textureManager = new TextureManager(this.showUrl);
  36. this.stageManager = new StageManager(this.textureManager, this.scriptManager);
  37. this.touchController = new TouchController();
  38. this.animationManager = new AnimationManager();
  39. this.orientationController = new OrientationController();
  40. this.activeHyperlinks = new Array();
  41. this.movieHyperlinks = new Array();
  42. // initialize default values
  43. this.script = null;
  44. this.currentSceneIndex = -1;
  45. this.nextSceneIndex = -1;
  46. this.currentSlideIndex = -1;
  47. this.previousSlideIndex = -1;
  48. this.currentSoundTrackIndex = 0;
  49. this.transformOriginValue = "";
  50. this.accumulatingDigits = false;
  51. this.digitAccumulator = 0;
  52. this.firstSlide = true;
  53. this.lastSlideViewedIndex = -1;
  54. this.accountID = "";
  55. this.guid = "";
  56. this.locale = "EN";
  57. this.isNavigationBarVisible = false;
  58. this.isFullscreen = false;
  59. this.volume = 3.0;
  60. this.muted = false;
  61. this.soundTrackPlayer = null;
  62. this.sceneIndexOfPrebuiltAnimations = -1;
  63. // store queued user action
  64. this.queuedUserAction = null;
  65. // events
  66. document.observe(kScriptDidDownloadEvent, this.handleScriptDidDownloadEvent.bind(this));
  67. document.observe(kScriptDidNotDownloadEvent, this.handleScriptDidNotDownloadEvent.bind(this));
  68. document.observe(kStageIsReadyEvent, this.handleStageIsReadyEvent.bind(this));
  69. document.observe(kStageSizeDidChangeEvent, this.handleStageSizeDidChangeEvent.bind(this));
  70. // swipe and keydown events
  71. document.observe(kKeyDownEvent, this.handleKeyDownEvent.bind(this));
  72. document.observe(kSwipeEvent, this.handleSwipeEvent.bind(this));
  73. // mouse event
  74. Event.observe(this.displayManager.body, "click", this.handleClickEvent.bind(this));
  75. // fullscreen change event
  76. document.observe(kFullscreenChangeEventName, this.handleFullscreenChangeEvent.bind(this));
  77. // windows resize event
  78. Event.observe(window, "resize", this.handleWindowResizeEvent.bind(this));
  79. // Can't use event observer for tap events
  80. // - this would cause the handler to be on a seperate event loop
  81. // invocation
  82. // - this would prevent us from doing this like opening new tabs (popup
  83. // blocker logic kicks in)
  84. this.touchController.registerTapEventCallback(this.handleTapEvent.bind(this));
  85. // Initialize state to Stopped
  86. this.changeState(kShowControllerState_Stopped);
  87. // movie cache to be used across different event timeline within one slide
  88. this.movieCache = null;
  89. // audio cache
  90. this.audioCache = null;
  91. // KPF playback controller
  92. this.playbackController = new KPFPlaybackController({}, this.stageManager.stage);
  93. // Navigator
  94. this.navigatorController = new NavigatorController(document.getElementById("slideshowNavigator"));
  95. // Slide number feedback
  96. this.slideNumberController = new SlideNumberController(document.getElementById("slideNumberControl"));
  97. // Slide number display
  98. this.slideNumberDisplay = new SlideNumberDisplay(document.getElementById("slideNumberDisplay"));
  99. // Help Placard
  100. this.helpPlacard = new HelpPlacardController(document.getElementById("helpPlacard"));
  101. // indicate if the show has recording
  102. this.isRecording = false;
  103. // a boolean to indicate if the recording is started
  104. this.isRecordingStarted = false;
  105. // IE9 does not support CSS animations
  106. if (isIE && browserVersion < 10) {
  107. this.animationSupported = false;
  108. }
  109. else {
  110. this.animationSupported = true;
  111. }
  112. // disable mouse right click context menu
  113. document.observe("contextmenu", this.handleContextMenuEvent.bind(this));
  114. },
  115. startShow: function() {
  116. this.changeState(kShowControllerState_DownloadingScript);
  117. this.scriptManager.downloadScript(this.delegate);
  118. },
  119. exitShow: function(endNow) {
  120. clearTimeout(this.exitTimeout);
  121. if (endNow) {
  122. this.delegate.showExited();
  123. } else {
  124. this.exitTimeout = setTimeout((function(){
  125. this.delegate.showExited();
  126. }).bind(this), 750);
  127. }
  128. },
  129. promptUserToTryAgain: function(message) {
  130. var tryAgain = false;
  131. tryAgain = confirm(message);
  132. return tryAgain;
  133. },
  134. handleScriptDidDownloadEvent: function(event) {
  135. switch (this.state) {
  136. case kShowControllerState_DownloadingScript:
  137. var script = this.script = event.memo.script;
  138. var showMode = script.showMode;
  139. if (showMode == kShowModeHyperlinksOnly) {
  140. this.displayManager.setHyperlinksOnlyMode();
  141. }
  142. this.changeState(kShowControllerState_Starting);
  143. // checking to see if a restarting scene index was specified in url...
  144. var sceneIndex;
  145. var restartingSceneIndex = parseInt(getUrlParameter("restartingSceneIndex"));
  146. // also look for a fragment identifier which can also indicate scene index
  147. var currentUrl = document.URL.split("?");
  148. var fragments = currentUrl[0].split("#");
  149. if (fragments[1]) {
  150. restartingSceneIndex = parseInt(fragments[1]);
  151. }
  152. if (restartingSceneIndex) {
  153. // found a restarting scene index, using that...restartingSceneIndex
  154. sceneIndex = restartingSceneIndex;
  155. } else {
  156. // checking to see if a starting slide number was specified in url...
  157. var startingSlide = getUrlParameter("currentSlide");
  158. var startingSlideNumber;
  159. if (startingSlide) {
  160. startingSlideNumber = parseInt(startingSlide);
  161. } else {
  162. // nope, not there, use 1...
  163. startingSlideNumber = 1;
  164. }
  165. sceneIndex = this.scriptManager.sceneIndexFromSlideIndex(startingSlideNumber - 1);
  166. }
  167. // if this show has recording, then we start the show in recording mode
  168. if (script.recording) {
  169. if (script.recording.eventTracks[0].type === "navigation") {
  170. this.narrationManager = new NarrationManager(script.recording);
  171. sceneIndex = this.narrationManager.sceneIndexFromNavigationEvent(this.narrationManager.navigationEvents[0]);
  172. this.isRecording = true;
  173. this.jumpToScene(sceneIndex, false);
  174. break;
  175. }
  176. }
  177. if (sceneIndex > script.lastSceneIndex) {
  178. break;
  179. }
  180. if (showMode === kShowModeAutoplay) {
  181. this.jumpToScene(sceneIndex, true);
  182. } else {
  183. var event = script.events[sceneIndex];
  184. var automaticPlay = event.automaticPlay == 1 || event.automaticPlay == true;
  185. this.jumpToScene(sceneIndex, automaticPlay);
  186. }
  187. break;
  188. default:
  189. debugMessage(kDebugShowController_HandleScriptDidDownloadEvent,
  190. "- hmmm we seem to have arrived here from an unpredicted state");
  191. break;
  192. }
  193. },
  194. handleScriptDidNotDownloadEvent: function(event) {
  195. debugMessage(kDebugShowController_HandleScriptDidNotDownloadEvent);
  196. var tryAgain = this.promptUserToTryAgain(kUnableToReachiWorkTryAgain);
  197. if (tryAgain) {
  198. this.scriptManager.downloadScript();
  199. } else {
  200. this.displayManager.clearLaunchMode();
  201. this.displayManager.hideWaitingIndicator();
  202. }
  203. },
  204. handleStageIsReadyEvent: function(event) {
  205. if (this.isFullscreen) {
  206. setTimeout((function() {
  207. this.displayManager.stageArea.style.opacity = 1;
  208. }).bind(this), 50)
  209. } else {
  210. setTimeout((function() {
  211. this.displayManager.stageArea.style.opacity = 1;
  212. }).bind(this), 500)
  213. }
  214. this.positionSlideNumberControl();
  215. this.positionSlideNumberDisplay();
  216. this.positionHelpPlacard();
  217. },
  218. positionSlideNumberControl: function() {
  219. var left = (this.displayManager.usableDisplayWidth - this.slideNumberController.width) / 2;
  220. var top = this.displayManager.stageAreaTop + this.displayManager.stageAreaHeight - (this.slideNumberController.height + 16);
  221. this.slideNumberController.setPosition(left, top);
  222. },
  223. positionSlideNumberDisplay: function() {
  224. var left = (this.displayManager.usableDisplayWidth - this.slideNumberDisplay.width) / 2;
  225. var top = this.displayManager.stageAreaTop + this.displayManager.stageAreaHeight - (this.slideNumberDisplay.height + 16);
  226. this.slideNumberDisplay.setPosition(left, top);
  227. },
  228. positionHelpPlacard: function() {
  229. var left = (this.displayManager.usableDisplayWidth - this.helpPlacard.width) / 2;
  230. var top = (this.displayManager.usableDisplayHeight - this.helpPlacard.height) / 2;
  231. this.helpPlacard.setPosition(left, top);
  232. },
  233. handleFullscreenChangeEvent: function() {
  234. if (document.webkitIsFullScreen || document.mozFullScreen) {
  235. this.isFullscreen = true;
  236. } else {
  237. this.isFullscreen = false;
  238. }
  239. setTimeout((function() {
  240. this.displayManager.layoutDisplay();
  241. }).bind(this), 0);
  242. },
  243. handleWindowResizeEvent: function() {
  244. clearTimeout(this.resizeTimer);
  245. this.resizeTimer = setTimeout(this.changeWindowSize.bind(this), 1000);
  246. },
  247. changeWindowSize: function() {
  248. if (this.delegate.setViewScale) {
  249. this.scriptManager.reapplyScaleFactor();
  250. this.textureManager.slideCache = null;
  251. this.textureManager.slideCache = {};
  252. var sceneIndexToUse = this.currentSceneIndex;
  253. if (this.state === kShowControllerState_IdleAtFinalState) {
  254. if (this.currentSceneIndex < this.script.numScenes - 1) {
  255. sceneIndexToUse = this.currentSceneIndex + 1;
  256. } else {
  257. if (this.script.loopSlideshow) {
  258. sceneIndexToUse = 0;
  259. }
  260. }
  261. }
  262. this.jumpToScene(sceneIndexToUse, false);
  263. }
  264. document.fire(kShowSizeDidChangeEvent, {
  265. width: this.script.slideWidth,
  266. height: this.script.slideHeight
  267. });
  268. },
  269. handleStageSizeDidChangeEvent: function(event) {
  270. // update TouchController with new track area
  271. this.touchController.setTrackArea(event.memo.left, event.memo.top, event.memo.width, event.memo.height);
  272. },
  273. handleKeyDownEvent: function(event) {
  274. var key = event.charCode || event.keyCode;
  275. // allow F11 and F12 to work on IE
  276. if (key === kKeyCode_F11 || key === kKeyCode_F12) {
  277. return;
  278. }
  279. var modifiers = {
  280. altKey: !!event.altKey,
  281. ctrlKey: !!event.ctrlKey,
  282. shiftKey: !!event.shiftKey,
  283. metaKey: !!event.metaKey
  284. };
  285. if (modifiers.metaKey) {
  286. if (key === kKeyCode_Period || key === kKeyCode_Dot) {
  287. // cmd - . to exit show
  288. this.exitShow(true);
  289. } else if (key != kKeyCode_Return) {
  290. // allow browsers to handle cmd-key
  291. return;
  292. }
  293. } else if (modifiers.ctrlKey) {
  294. // allow browsers to handle ctrl-key
  295. return;
  296. }
  297. event.stop();
  298. this.onKeyPress(key, modifiers);
  299. },
  300. handleContextMenuEvent: function(event) {
  301. event.stop();
  302. },
  303. handleClickEvent: function(event) {
  304. if (this.isRecording) {
  305. return;
  306. }
  307. var x, y;
  308. if (event.pageX || event.pageY) {
  309. x = event.pageX;
  310. y = event.pageY;
  311. } else {
  312. x = event.clientX;
  313. y = event.clientY;
  314. }
  315. var displayCoOrds = {
  316. pointX: x,
  317. pointY: y
  318. };
  319. // for IE make sure windows has focus
  320. if (isIE) {
  321. window.focus();
  322. }
  323. // For video element, let the event to propagate
  324. if (event.target.nodeName.toLowerCase() === "video") {
  325. return;
  326. }
  327. this.processClickOrTapAtDisplayCoOrds(displayCoOrds);
  328. },
  329. handleTapEvent: function(event) {
  330. var displayCoOrds = {
  331. pointX: event.memo.pointX,
  332. pointY: event.memo.pointY
  333. };
  334. var target = event.memo.target;
  335. var slideNumber;
  336. if (target) {
  337. slideNumber = this.slideNumberFromTarget(target);
  338. }
  339. if (slideNumber) {
  340. this.navigatorController.select(slideNumber);
  341. } else {
  342. this.processClickOrTapAtDisplayCoOrds(displayCoOrds);
  343. }
  344. },
  345. slideNumberFromTarget: function(target) {
  346. // return null if there is no target
  347. if (!target) {
  348. return null;
  349. }
  350. // search up to the node below body node
  351. while (target.slideNumber == null && target.nodeName.toLowerCase() !== "body") {
  352. target = target.parentNode;
  353. }
  354. return target.slideNumber;
  355. },
  356. processClickOrTapAtDisplayCoOrds: function(displayCoOrds) {
  357. var isHyperlink = false;
  358. var hyperlink;
  359. if (this.slideNumberController.isShowing) {
  360. if (this.slideNumberTimeout) {
  361. clearTimeout(this.slideNumberTimeout);
  362. }
  363. this.slideNumberTimeout = setTimeout(this.hideAndResetSlideNumberController.bind(this), 0);
  364. return;
  365. }
  366. if (this.helpPlacard.isShowing) {
  367. this.helpPlacard.hide();
  368. return;
  369. }
  370. var showCoOrds = this.displayManager.convertDisplayCoOrdsToShowCoOrds(displayCoOrds);
  371. if (showCoOrds.pointX != -1) {
  372. hyperlink = this.findHyperlinkAtCoOrds(showCoOrds);
  373. }
  374. if (hyperlink) {
  375. this.processHyperlink(hyperlink);
  376. } else {
  377. this.advanceToNextBuild("processClickOrTapAtDisplayCoOrds");
  378. }
  379. },
  380. handleSwipeEvent: function(event) {
  381. var memo = event.memo;
  382. var direction = memo.direction;
  383. // if the navigator is showing then hide it when swiping either left or right
  384. if (this.displayManager.navigatorIsShowing && (direction === "left" || direction === "right")) {
  385. this.navigatorController.thumbnailSidebar.hide(this.navigatorController.leftSidebar);
  386. return;
  387. }
  388. // Toggle the slide navigator if swipe event is in navigator area.
  389. // The thumbnail scroller is 129px. For now we use 150px but can be adjusted later if needed.
  390. if (memo.swipeStartX && memo.swipeStartX < 150) {
  391. if (memo.direction === "right") {
  392. this.navigatorController.thumbnailSidebar.show(this.navigatorController.leftSidebar);
  393. } else if (memo.direction === "left") {
  394. this.navigatorController.thumbnailSidebar.hide(this.navigatorController.leftSidebar);
  395. }
  396. return;
  397. }
  398. if (event.memo.direction === "left") {
  399. switch (event.memo.fingers) {
  400. case 1:
  401. this.advanceToNextBuild("handleSwipeEvent");
  402. break;
  403. case 2:
  404. this.advanceToNextSlide("handleSwipeEvent");
  405. break;
  406. default:
  407. break;
  408. }
  409. } else if (event.memo.direction === "right") {
  410. switch (event.memo.fingers) {
  411. case 1:
  412. this.goBackToPreviousSlide("handleSwipeEvent");
  413. break;
  414. case 2:
  415. this.goBackToPreviousBuild("handleSwipeEvent");
  416. break;
  417. default:
  418. break;
  419. }
  420. }
  421. },
  422. onMouseDown: function(mouseDownEvent) {
  423. if (mouseDownEvent.leftClick) {
  424. this.advanceToNextBuild("onMouseDown");
  425. } else if (mouseDownEvent.rightClick) {
  426. this.goBackToPreviousBuild("onMouseDown");
  427. }
  428. },
  429. onKeyPress: function(key, modifier) {
  430. if ((key >= kKeyCode_Numeric_0) && (key <= kKeyCode_Numeric_9)) {
  431. key = kKeyCode_0 + (key - kKeyCode_Numeric_0);
  432. }
  433. key += (modifier.shiftKey ? kKeyModifier_Shift : 0);
  434. key += (modifier.altKey ? kKeyModifier_Alt : 0);
  435. key += (modifier.ctrlKey ? kKeyModifier_Ctrl : 0);
  436. key += (modifier.metaKey ? kKeyModifier_Meta : 0);
  437. if (this.isRecording) {
  438. return;
  439. }
  440. var digitEncountered = false;
  441. switch (key) {
  442. case kKeyCode_Escape:
  443. this.exitShow(true);
  444. break;
  445. /*
  446. * case kKeyCode_Return + kKeyModifier_Ctrl:
  447. * this.displayManager.showWaitingIndicator(); break;
  448. *
  449. * case kKeyCode_Return + kKeyModifier_Alt:
  450. * this.displayManager.hideWaitingIndicator(); break;
  451. *
  452. * case kKeyCode_Return + kKeyModifier_Meta: this.debugDiagnosticDump();
  453. * break;
  454. */
  455. case kKeyCode_Slash:
  456. case kKeyCode_Slash + kKeyModifier_Shift:
  457. if (this.helpPlacard.isShowing) {
  458. this.helpPlacard.hide();
  459. } else {
  460. this.helpPlacard.show();
  461. }
  462. break;
  463. case kKeyCode_Q:
  464. this.exitShow(true);
  465. break;
  466. case kKeyCode_S:
  467. if (this.slideNumberController.isShowing) {
  468. if (this.slideNumberTimeout) {
  469. clearTimeout(this.slideNumberTimeout);
  470. }
  471. this.slideNumberTimeout = setTimeout(this.hideAndResetSlideNumberController.bind(this), 0);
  472. }
  473. if (this.slideNumberDisplay.isShowing) {
  474. this.slideNumberDisplay.hide();
  475. } else {
  476. this.slideNumberDisplay.setSlideNumber(this.currentSlideIndex + 1);
  477. this.slideNumberDisplay.show();
  478. }
  479. break;
  480. case kKeyCode_Return:
  481. if (this.accumulatingDigits) {
  482. // return pressed while accumulating digits.
  483. this.accumulatingDigits = false;
  484. if (this.script.showMode != kShowModeHyperlinksOnly) {
  485. if (this.digitAccumulator > this.script.slideCount) {
  486. this.digitAccumulator = this.script.slideCount;
  487. }
  488. else if (this.digitAccumulator < 1) {
  489. this.digitAccumulator = 1;
  490. }
  491. this.slideNumberController.setSlideNumber(this.digitAccumulator);
  492. this.jumpToSlide(this.digitAccumulator);
  493. } else {
  494. debugMessage(kDebugShowController_OnKeyPress, "- can't do it, we're in hyperlinks only mode");
  495. }
  496. break;
  497. }
  498. // fall through
  499. case kKeyCode_N:
  500. case kKeyCode_Space:
  501. case kKeyCode_DownArrow:
  502. case kKeyCode_RightArrow:
  503. case kKeyCode_PageDown:
  504. // advance to next build...
  505. this.advanceToNextBuild("onKeyPress");
  506. break;
  507. case kKeyCode_RightArrow + kKeyModifier_Shift:
  508. case kKeyCode_CloseBracket:
  509. // advance and skip build...
  510. this.advanceAndSkipBuild("onKeyPress");
  511. break;
  512. case kKeyCode_DownArrow + kKeyModifier_Shift:
  513. case kKeyCode_PageDown + kKeyModifier_Shift:
  514. case kKeyCode_CloseBracket:
  515. case kKeyCode_Equal + kKeyModifier_Shift:
  516. case kKeyCode_Equal:
  517. case kKeyCode_Plus:
  518. // advance to next slide...
  519. this.advanceToNextSlide("onKeyPress");
  520. break;
  521. case kKeyCode_LeftArrow + kKeyModifier_Shift:
  522. case kKeyCode_PageUp + kKeyModifier_Shift:
  523. case kKeyCode_OpenBracket:
  524. // go back to previous build...
  525. this.goBackToPreviousBuild("onKeyPress");
  526. break;
  527. case kKeyCode_P:
  528. case kKeyCode_PageUp:
  529. case kKeyCode_LeftArrow:
  530. case kKeyCode_UpArrow:
  531. case kKeyCode_UpArrow + kKeyModifier_Shift:
  532. case kKeyCode_Hyphen:
  533. case kKeyCode_Minus:
  534. // go back to previous slide...
  535. this.goBackToPreviousSlide("onKeyPress");
  536. break;
  537. case kKeyCode_Delete:
  538. digitEncountered = true;
  539. if (this.accumulatingDigits) {
  540. if (this.digitAccumulator < 10) {
  541. if (this.slideNumberTimeout) {
  542. clearTimeout(this.slideNumberTimeout);
  543. }
  544. this.slideNumberTimeout = setTimeout(this.hideAndResetSlideNumberController.bind(this), 0);
  545. }
  546. else {
  547. if (this.slideNumberTimeout) {
  548. clearTimeout(this.slideNumberTimeout);
  549. }
  550. this.slideNumberTimeout = setTimeout(this.hideAndResetSlideNumberController.bind(this), 7000);
  551. var digit = this.digitAccumulator.toString();
  552. this.digitAccumulator = parseInt(digit.substring(0, digit.length - 1));
  553. this.slideNumberController.setSlideNumber(this.digitAccumulator);
  554. }
  555. }
  556. break;
  557. case kKeyCode_Home:
  558. // go back to first slide...
  559. if (this.script.showMode != kShowModeHyperlinksOnly) {
  560. this.jumpToSlide(1);
  561. } else {
  562. debugMessage(kDebugShowController_OnKeyPress, "- can't do it, we're in hyperlinks only mode");
  563. }
  564. break;
  565. case kKeyCode_End:
  566. // go back to last slide...
  567. if (this.script.showMode != kShowModeHyperlinksOnly) {
  568. this.jumpToSlide(this.script.slideCount);
  569. } else {
  570. debugMessage(kDebugShowController_OnKeyPress, "- can't do it, we're in hyperlinks only mode");
  571. }
  572. break;
  573. default:
  574. if (this.slideNumberTimeout) {
  575. clearTimeout(this.slideNumberTimeout);
  576. }
  577. this.slideNumberTimeout = setTimeout(this.hideAndResetSlideNumberController.bind(this), 7000);
  578. if ((key >= kKeyCode_0) && (key <= kKeyCode_9)) {
  579. if (this.slideNumberDisplay.isShowing) {
  580. this.slideNumberDisplay.hide();
  581. }
  582. digitEncountered = true;
  583. if (this.accumulatingDigits === false) {
  584. // digit entered, start accumulating digits...
  585. this.accumulatingDigits = true;
  586. this.digitAccumulator = 0;
  587. }
  588. if (this.digitAccumulator.toString().length < 4) {
  589. this.digitAccumulator *= 10;
  590. this.digitAccumulator += (key - kKeyCode_0);
  591. this.slideNumberController.setSlideNumber(this.digitAccumulator);
  592. if (!this.slideNumberController.isShowing) {
  593. this.slideNumberController.show();
  594. }
  595. }
  596. }
  597. else {
  598. digitEncountered = true;
  599. }
  600. break;
  601. }
  602. if (this.accumulatingDigits && (digitEncountered === false)) {
  603. // non-digit entered, stop accumulating digits...
  604. //this.accumulatingDigits = false;
  605. //this.digitAccumulator = 0;
  606. }
  607. },
  608. hideAndResetSlideNumberController: function() {
  609. if (this.slideNumberTimeout) {
  610. clearTimeout(this.slideNumberTimeout);
  611. }
  612. this.accumulatingDigits = false;
  613. this.digitAccumulator = 0;
  614. this.slideNumberController.hide();
  615. },
  616. hideSlideNumberDisplay: function() {
  617. this.slideNumberDisplay.hide();
  618. },
  619. toggleFullscreen: function() {
  620. // IE does not support fullscreen mode, return for now
  621. if (isIE) {
  622. return;
  623. }
  624. setTimeout((function() {
  625. this.displayManager.stageArea.style.opacity = 0;
  626. }).bind(this), 0);
  627. // hide hud immediately
  628. this.displayManager.hideHUD(true);
  629. if (document.webkitIsFullScreen || document.mozFullScreen) {
  630. this.isFullscreen = false;
  631. (document.webkitCancelFullScreen && document.webkitCancelFullScreen())
  632. || (document.mozCancelFullScreen && document.mozCancelFullScreen());
  633. } else {
  634. this.isFullscreen = true;
  635. (document.body.webkitRequestFullScreen && document.body.webkitRequestFullScreen())
  636. || (document.body.mozRequestFullScreen && document.body.mozRequestFullScreen());
  637. }
  638. },
  639. // State Management
  640. // ================
  641. changeState: function(newState) {
  642. if (newState != this.state) {
  643. //this.accumulatingDigits = false;
  644. //this.digitAccumulator = 0;
  645. this.leavingState();
  646. this.state = newState;
  647. this.enteringState();
  648. }
  649. },
  650. leavingState: function() {
  651. switch (this.state) {
  652. case kShowControllerState_Stopped:
  653. break;
  654. case kShowControllerState_Starting:
  655. break;
  656. case kShowControllerState_SettingUpScene:
  657. break;
  658. case kShowControllerState_IdleAtFinalState:
  659. break;
  660. case kShowControllerState_IdleAtInitialState:
  661. break;
  662. case kShowControllerState_WaitingToJump:
  663. break;
  664. case kShowControllerState_ReadyToJump:
  665. break;
  666. case kShowControllerState_WaitingToPlay:
  667. this.displayManager.hideWaitingIndicator();
  668. break;
  669. case kShowControllerState_ReadyToPlay:
  670. break;
  671. case kShowControllerState_Playing:
  672. break;
  673. }
  674. },
  675. enteringState: function() {
  676. switch (this.state) {
  677. case kShowControllerState_Stopped:
  678. break;
  679. case kShowControllerState_Starting:
  680. this.displayManager.showWaitingIndicator();
  681. break;
  682. case kShowControllerState_SettingUpScene:
  683. break;
  684. case kShowControllerState_IdleAtFinalState:
  685. // unload slide cache not next to the current slide
  686. this.unloadTextures();
  687. case kShowControllerState_IdleAtInitialState:
  688. this.updateSlideNumber();
  689. runInNextEventLoop(this.doIdleProcessing.bind(this));
  690. break;
  691. case kShowControllerState_WaitingToJump:
  692. // don't show spinner here, do it in pollForSceneToLoad after a few
  693. // polls so the spinner doesn't come up right away
  694. break;
  695. case kShowControllerState_ReadyToJump:
  696. break;
  697. case kShowControllerState_WaitingToPlay:
  698. this.displayManager.showWaitingIndicator();
  699. break;
  700. case kShowControllerState_ReadyToPlay:
  701. break;
  702. case kShowControllerState_Playing:
  703. break;
  704. }
  705. },
  706. preloadTextures: function() {
  707. var script = this.script;
  708. var sceneIndexToUse = this.currentSceneIndex;
  709. if (this.state === kShowControllerState_IdleAtFinalState) {
  710. if (sceneIndexToUse < script.numScenes - 1) {
  711. sceneIndexToUse = sceneIndexToUse + 1;
  712. } else if (script.loopSlideshow) {
  713. sceneIndexToUse = 0;
  714. }
  715. }
  716. // preload textures
  717. this.textureManager.loadScene(sceneIndexToUse);
  718. },
  719. unloadTextures: function() {
  720. var script = this.script;
  721. var sceneIndexToUse = this.currentSceneIndex;
  722. if (this.state === kShowControllerState_IdleAtFinalState) {
  723. if (sceneIndexToUse < script.numScenes - 1) {
  724. sceneIndexToUse = sceneIndexToUse + 1;
  725. } else if (script.loopSlideshow) {
  726. sceneIndexToUse = 0;
  727. }
  728. }
  729. var currentSlideIndex = script.slideIndexFromSceneIndexLookup[sceneIndexToUse];
  730. var slideCache = this.textureManager.slideCache;
  731. for (var index in slideCache) {
  732. // detect current slide index and its previous and next slide in slideCache buffer
  733. if (index < currentSlideIndex - 1 || index > currentSlideIndex + 1) {
  734. // remove slide cache
  735. var cache = slideCache[index];
  736. for (var textureId in cache.textureAssets) {
  737. var canvas = cache.textureAssets[textureId];
  738. if (canvas) {
  739. // clear canvas object
  740. var context = canvas.getContext("2d");
  741. if (context) {
  742. context.clearRect(0, 0, canvas.width, canvas.height);
  743. }
  744. // remove reference
  745. delete cache.textureAssets[textureId];
  746. }
  747. }
  748. delete this.textureManager.slideCache[index].textureAssets;
  749. delete this.textureManager.slideCache[index].textureRequests;
  750. delete this.textureManager.slideCache[index].requested;
  751. // call internal pdf document destroy method
  752. if (cache.pdf) {
  753. cache.pdf.destroy();
  754. delete this.textureManager.slideCache[index].pdf;
  755. }
  756. // finally remove slide cache reference
  757. delete this.textureManager.slideCache[index];
  758. }
  759. }
  760. },
  761. doIdleProcessing: function() {
  762. // preload textures for next slide if applicable
  763. this.preloadTextures();
  764. if (this.queuedUserAction != null) {
  765. // executing queued user action...
  766. this.queuedUserAction();
  767. this.queuedUserAction = null;
  768. } else {
  769. var stage = this.stageManager.stage;
  770. if (stage.childNodes.length !== 0) {
  771. this.updateNavigationButtons();
  772. }
  773. }
  774. // create hyperlink in setTimeout using background thread
  775. clearTimeout(this.createHyperlinksForCurrentStateTimeout);
  776. this.createHyperlinksForCurrentStateTimeout = setTimeout((function() {this.createHyperlinksForCurrentState("idle");}).bind(this), 100);
  777. },
  778. truncatedSlideIndex: function(slideIndex) {
  779. return this.truncatedIndex(slideIndex, this.script.lastSlideIndex, this.script.loopSlideshow);
  780. },
  781. truncatedSceneIndex: function(sceneIndex) {
  782. return this.truncatedIndex(sceneIndex, this.script.lastSceneIndex, this.script.loopSlideshow);
  783. },
  784. truncatedIndex: function(index, lastIndex, isLooping) {
  785. if (index < 0) {
  786. if (isLooping) {
  787. index = index + lastIndex + 1;
  788. } else {
  789. index = -1;
  790. }
  791. } else if (index > lastIndex) {
  792. if (isLooping) {
  793. index = index - lastIndex - 1;
  794. } else {
  795. index = -1;
  796. }
  797. }
  798. return index;
  799. },
  800. advanceToNextBuild: function(context) {
  801. // do not proceed if the script is not available
  802. if (!this.script) {
  803. return false;
  804. }
  805. if (this.script.showMode === kShowModeHyperlinksOnly && context != "currentSceneDidComplete") {
  806. return false;
  807. }
  808. if (this.displayManager.infoPanelIsShowing) {
  809. return false;
  810. }
  811. var result = false;
  812. switch (this.state) {
  813. case kShowControllerState_IdleAtFinalState:
  814. if (this.nextSceneIndex === -1) {
  815. if (this.delegate.getKPFJsonStringForShow) {
  816. this.stopSoundTrack();
  817. this.exitShow();
  818. } else {
  819. this.stopSoundTrack();
  820. break;
  821. }
  822. }
  823. // idle on final state, jump to next scene
  824. result = true;
  825. this.jumpToScene(this.nextSceneIndex, true);
  826. break;
  827. case kShowControllerState_IdleAtInitialState:
  828. if (this.currentSceneIndex >= this.script.numScenes) {
  829. if (this.script.loopSlideshow) {
  830. // we're at the end but this IS a looping show, jump to start
  831. result = true;
  832. this.jumpToScene(0, false);
  833. } else {
  834. if (this.delegate.getKPFJsonStringForShow) {
  835. this.stopSoundTrack();
  836. this.exitShow();
  837. } else {
  838. this.stopSoundTrack();
  839. break;
  840. }
  841. }
  842. } else {
  843. // we're sitting idle on initial state, preload next scene and play current scene
  844. result = true;
  845. this.playCurrentScene();
  846. }
  847. break;
  848. default:
  849. debugMessage(kDebugShowController_AdvanceToNextBuild, "nextSceneIndex: " + this.nextSceneIndex + " can't advance now, not in an idle state (currently in '" + this.state + "' state), queue up action to run in next idle time");
  850. if (this.queuedUserAction == null) {
  851. result = true;
  852. this.queuedUserAction = this.advanceToNextBuild.bind(this, context);
  853. }
  854. break;
  855. }
  856. return result;
  857. },
  858. advanceToNextSlide: function(context) {
  859. // do not proceed if the script is not available
  860. if (!this.script) {
  861. return false;
  862. }
  863. if (this.script.showMode == kShowModeHyperlinksOnly) {
  864. return;
  865. }
  866. if (this.displayManager.infoPanelIsShowing) {
  867. return;
  868. }
  869. var sceneIndexToUse = this.currentSceneIndex;
  870. switch (this.state) {
  871. case kShowControllerState_IdleAtFinalState:
  872. sceneIndexToUse = sceneIndexToUse + 1;
  873. // Fall through
  874. case kShowControllerState_IdleAtInitialState:
  875. var currentSlideIndex = this.scriptManager.slideIndexFromSceneIndex(sceneIndexToUse);
  876. var nextSlideIndex ;
  877. if (currentSlideIndex === this.script.slideCount - 1) {
  878. if (this.script.loopSlideshow) {
  879. nextSlideIndex = 0;
  880. } else {
  881. return;
  882. }
  883. } else {
  884. nextSlideIndex = this.currentSlideIndex + 1;
  885. }
  886. var sceneIndex = this.scriptManager.sceneIndexFromSlideIndex(nextSlideIndex);
  887. var event = this.script.events[sceneIndex];
  888. var automaticPlay = event.automaticPlay == 1 || event.automaticPlay == true;
  889. this.jumpToSlide(nextSlideIndex + 1, automaticPlay);
  890. break;
  891. default:
  892. debugMessage(kDebugShowController_AdvanceToNextSlide, "can't advance now, not in an idle state (currently in '" + this.state + "' state), queue up action to run in next idle time");
  893. if (this.queuedUserAction == null) {
  894. this.queuedUserAction = this.advanceToNextSlide.bind(this, context);
  895. }
  896. break;
  897. }
  898. },
  899. goBackToPreviousBuild: function(context) {
  900. // do not proceed if the script is not available
  901. if (!this.script) {
  902. return false;
  903. }
  904. // going back to previous build, remove all media cache
  905. this.resetMediaCache();
  906. if (this.script.showMode == kShowModeHyperlinksOnly) {
  907. return;
  908. }
  909. if (this.displayManager.infoPanelIsShowing) {
  910. return;
  911. }
  912. var sceneIndexToUse = this.currentSceneIndex;
  913. switch (this.state) {
  914. case kShowControllerState_IdleAtFinalState:
  915. sceneIndexToUse = sceneIndexToUse + 1;
  916. // Fall through
  917. case kShowControllerState_Playing:
  918. case kShowControllerState_IdleAtInitialState:
  919. var previousSceneIndex ;
  920. if (sceneIndexToUse === 0) {
  921. if (this.script.loopSlideshow) {
  922. previousSceneIndex = this.script.events.length - 1;
  923. } else {
  924. return;
  925. }
  926. } else {
  927. previousSceneIndex = sceneIndexToUse - 1;
  928. }
  929. this.jumpToScene(previousSceneIndex, false);
  930. break;
  931. default:
  932. debugMessage(kDebugShowController_GoBackToPreviousBuild, "can't go back now, not in an idle state (currently in '" + this.state + "' state)");
  933. if (this.queuedUserAction == null) {
  934. this.queuedUserAction = this.goBackToPreviousBuild.bind(this, context);
  935. }
  936. break;
  937. }
  938. },
  939. advanceAndSkipBuild: function(context) {
  940. // do not proceed if the script is not available
  941. if (!this.script) {
  942. return false;
  943. }
  944. if (this.script.showMode == kShowModeHyperlinksOnly) {
  945. return;
  946. }
  947. var sceneIndexToUse = this.currentSceneIndex;
  948. switch (this.state) {
  949. case kShowControllerState_IdleAtFinalState:
  950. sceneIndexToUse = sceneIndexToUse + 1;
  951. // Fall through
  952. case kShowControllerState_IdleAtInitialState:
  953. var nextSceneIndex;
  954. if (sceneIndexToUse >= this.script.numScenes - 1) {
  955. if (this.script.loopSlideshow) {
  956. nextSceneIndex = 0;
  957. } else {
  958. return;
  959. }
  960. } else {
  961. nextSceneIndex = sceneIndexToUse + 1;
  962. }
  963. this.jumpToScene(nextSceneIndex, false);
  964. break;
  965. default:
  966. debugMessage(kDebugShowController_GoBackToPreviousBuild, "can't go back now, not in an idle state (currently in '" + this.state + "' state)");
  967. if (this.queuedUserAction == null) {
  968. this.queuedUserAction = this.advanceAndSkipBuild.bind(this, context);
  969. }
  970. break;
  971. }
  972. },
  973. goBackToPreviousSlide: function(context) {
  974. // do not proceed if the script is not available
  975. if (!this.script) {
  976. return false;
  977. }
  978. if (this.script.showMode == kShowModeHyperlinksOnly) {
  979. return;
  980. }
  981. if (this.displayManager.infoPanelIsShowing) {
  982. return;
  983. }
  984. var sceneIndexToUse = this.currentSceneIndex;
  985. switch (this.state) {
  986. case kShowControllerState_IdleAtFinalState:
  987. sceneIndexToUse = sceneIndexToUse + 1;
  988. // Fall through
  989. case kShowControllerState_Playing:
  990. case kShowControllerState_IdleAtInitialState:
  991. var currentSlideIndex = this.scriptManager.slideIndexFromSceneIndex(sceneIndexToUse);
  992. var sceneIndexForCurrentSlideIndex = this.scriptManager.sceneIndexFromSlideIndex(currentSlideIndex);
  993. var previousSlideIndex;
  994. if (currentSlideIndex === 0) {
  995. if (sceneIndexToUse > 0) {
  996. // if we are not on first build of the slide, go back to first build of the slide
  997. previousSlideIndex = 0;
  998. } else {
  999. if (this.script.loopSlideshow) {
  1000. previousSlideIndex = this.script.slideCount - 1;
  1001. } else {
  1002. previousSlideIndex = 0;
  1003. }
  1004. }
  1005. } else if (currentSlideIndex === -1 && sceneIndexToUse > 0) {
  1006. previousSlideIndex = this.script.slideCount - 1;
  1007. } else {
  1008. if (sceneIndexToUse > sceneIndexForCurrentSlideIndex) {
  1009. // if we are not on first build of the slide, go back to first build of the slide
  1010. previousSlideIndex = this.currentSlideIndex;
  1011. } else {
  1012. // if we are on first build of the slide, go back to previous slide
  1013. previousSlideIndex = this.currentSlideIndex - 1;
  1014. }
  1015. }
  1016. this.jumpToSlide(previousSlideIndex + 1);
  1017. break;
  1018. default:
  1019. debugMessage(kDebugShowController_GoBackToPreviousSlide, "can't go back now, not in an idle state (currently in '" + this.state + "' state)");
  1020. if (this.queuedUserAction == null) {
  1021. this.queuedUserAction = this.goBackToPreviousSlide.bind(this, context);
  1022. }
  1023. break;
  1024. }
  1025. },
  1026. calculatePreviousSceneIndex: function(sceneIndex) {
  1027. if (sceneIndex == -1) {
  1028. previousSceneIndex = -1;
  1029. }
  1030. else {
  1031. previousSceneIndex = sceneIndex - 1;
  1032. }
  1033. return previousSceneIndex;
  1034. },
  1035. jumpToSlide: function(slideNumber, automaticPlay) {
  1036. var slideIndex = slideNumber - 1;
  1037. var sceneIndex = this.scriptManager.sceneIndexFromSlideIndex(slideIndex);
  1038. // we are jumping to slide, remove all media cache
  1039. this.resetMediaCache();
  1040. // enable automatic play when the slide it advances to has automatic play
  1041. // see <rdar://problem/12781266> Next slide does not auto advance when using keyboard to advance without transiti
  1042. if (automaticPlay == null) {
  1043. automaticPlay = false;
  1044. }
  1045. this.jumpToScene(sceneIndex, automaticPlay);
  1046. },
  1047. jumpToScene: function(sceneIndex, playAnimations) {
  1048. this.lastSlideViewedIndex = this.scriptManager.slideIndexFromSceneIndex(this.currentSceneIndex);
  1049. if (sceneIndex === -1) {
  1050. return;
  1051. }
  1052. switch (this.state) {
  1053. case kShowControllerState_Starting:
  1054. // There is a bug in webkit - cursor not being able to change if sitting idle unless it has been moved
  1055. // The workaround for the bug without moving the mouse cursor is to change DOM structure to force a redraw
  1056. // References: http://code.google.com/p/chromium/issues/detail?id=26723
  1057. var cssText = "position:absolute;background-color:transparent; left:0px; top:0px; width:" + this.displayManager.usableDisplayWidth +"px; height:" + this.displayManager.usableDisplayHeight + "px;";
  1058. this.starting = true;
  1059. this.maskElement = document.createElement("div");
  1060. this.maskElement.setAttribute("style",cssText);
  1061. document.body.appendChild(this.maskElement);
  1062. case kShowControllerState_IdleAtInitialState:
  1063. case kShowControllerState_IdleAtFinalState:
  1064. case kShowControllerState_ReadyToJump:
  1065. break;
  1066. default:
  1067. debugMessage(kDebugShowController_JumpToScene, "can't jump now, currently in '" + this.state + "' state which does not supports jumping...");
  1068. return;
  1069. }
  1070. if (this.textureManager.isScenePreloaded(sceneIndex) === false) {
  1071. this.changeState(kShowControllerState_WaitingToJump);
  1072. // loadScene with callback handler and params
  1073. var sceneToLoadInfo = {
  1074. sceneIndex: sceneIndex,
  1075. automaticPlay: playAnimations
  1076. };
  1077. this.waitForSceneToLoadTimeout = setTimeout(this.handleSceneDidNotLoad.bind(this, sceneToLoadInfo), kMaxSceneDownloadWaitTime);
  1078. this.textureManager.loadScene(sceneIndex, this.handleSceneDidLoad.bind(this, sceneToLoadInfo));
  1079. return;
  1080. }
  1081. this.changeState(kShowControllerState_SettingUpScene);
  1082. runInNextEventLoop(this.jumpToScene_partThree.bind(this, sceneIndex, playAnimations));
  1083. },
  1084. handleSceneDidLoad: function(sceneToLoadInfo) {
  1085. clearTimeout(this.waitForSceneToLoadTimeout);
  1086. switch (this.state) {
  1087. case kShowControllerState_WaitingToJump:
  1088. this.changeState(kShowControllerState_ReadyToJump);
  1089. this.jumpToScene_partTwo(sceneToLoadInfo.sceneIndex, sceneToLoadInfo.automaticPlay);
  1090. break;
  1091. default:
  1092. break;
  1093. }
  1094. },
  1095. handleSceneDidNotLoad: function(sceneToLoadInfo) {
  1096. clearTimeout(this.waitForSceneToLoadTimeout);
  1097. this.queuedUserAction = null;
  1098. var tryAgain = this.promptUserToTryAgain(kUnableToReachiWorkTryAgain);
  1099. if (tryAgain) {
  1100. // restarting player with sceneIndex
  1101. var currentUrl = window.location.href;
  1102. var croppedUrl;
  1103. var indexOfRestartParam = currentUrl.indexOf("&restartingSceneIndex");
  1104. if (indexOfRestartParam === -1) {
  1105. croppedUrl = currentUrl;
  1106. } else {
  1107. croppedUrl = currentUrl.substring(0, indexOfRestartParam);
  1108. }
  1109. var newUrl = croppedUrl + "&restartingSceneIndex=" + sceneToLoadInfo.sceneIndex;
  1110. window.location.replace(newUrl);
  1111. } else {
  1112. this.changeState(kShowControllerState_IdleAtFinalState);
  1113. }
  1114. },
  1115. jumpToScene_partTwo: function(sceneIndex, playAnimations) {
  1116. this.changeState(kShowControllerState_SettingUpScene);
  1117. // state changed (UI controls should disable), run partThree in next event loop
  1118. runInNextEventLoop(this.jumpToScene_partThree.bind(this, sceneIndex, playAnimations));
  1119. },
  1120. jumpToScene_partThree: function(sceneIndex, playAnimations) {
  1121. var delayBeforeNextPart = false;
  1122. if (delayBeforeNextPart) {
  1123. runInNextEventLoop(this.jumpToScene_partFour.bind(this, sceneIndex, playAnimations));
  1124. } else {
  1125. this.jumpToScene_partFour(sceneIndex, playAnimations);
  1126. }
  1127. },
  1128. jumpToScene_partFour: function(sceneIndex, playAnimations) {
  1129. this.displayScene(sceneIndex);
  1130. if (this.starting) {
  1131. // There is a bug in webkit - cursor not being able to change if sitting idle unless it has been moved
  1132. // The workaround for the bug without moving the mouse cursor is to change DOM structure to force a redraw
  1133. // References: http://code.google.com/p/chromium/issues/detail?id=26723
  1134. if (this.maskElement != null) {
  1135. document.body.removeChild(this.maskElement);
  1136. this.maskElement = null;
  1137. this.starting = false;
  1138. }
  1139. window.focus();
  1140. }
  1141. if (this.helpPlacard.isShowing) {
  1142. this.helpPlacard.hide();
  1143. }
  1144. if (this.slideNumberDisplay.isShowing) {
  1145. this.slideNumberDisplay.hide();
  1146. }
  1147. if (this.slideNumberController.isShowing) {
  1148. if (this.slideNumberTimeout) {
  1149. clearTimeout(this.slideNumberTimeout);
  1150. }
  1151. this.slideNumberTimeout = setTimeout(this.hideAndResetSlideNumberController.bind(this), 500);
  1152. }
  1153. if (playAnimations) {
  1154. var script = this.script;
  1155. if (script.showMode === kShowModeAutoplay) {
  1156. var event = script.events[sceneIndex];
  1157. var effects = event.effects;
  1158. if (effects && effects.length > 0) {
  1159. var delay = effects[0].type === "transition" ? script.autoplayTransitionDelay : script.autoplayBuildDelay;
  1160. setTimeout((function(){this.playCurrentScene();}).bind(this), delay * 1000);
  1161. } else {
  1162. this.playCurrentScene();
  1163. }
  1164. } else {
  1165. this.playCurrentScene();
  1166. }
  1167. } else {
  1168. this.changeState(kShowControllerState_IdleAtInitialState);
  1169. if (this.isRecording && !this.isRecordingStarted) {
  1170. this.narrationManager.start();
  1171. this.isRecordingStarted = true;
  1172. }
  1173. }
  1174. },
  1175. displayScene: function(sceneIndex, hyperlinkEvent) {
  1176. if (sceneIndex === -1) {
  1177. return;
  1178. }
  1179. // remove all css
  1180. this.animationManager.deleteAllAnimations();
  1181. // clean up media cache if we are advancing to different slide
  1182. var outgoingSlideIndex = this.scriptManager.slideIndexFromSceneIndex(this.currentSceneIndex);
  1183. var incomingSlideIndex = hyperlinkEvent ? hyperlinkEvent.slideIndex : this.scriptManager.slideIndexFromSceneIndex(sceneIndex);
  1184. if (outgoingSlideIndex !== incomingSlideIndex) {
  1185. this.resetMediaCache();
  1186. }
  1187. // set currentSceneIndex
  1188. this.setCurrentSceneIndexTo(sceneIndex);
  1189. if (hyperlinkEvent) {
  1190. this.playbackController.renderEvent(hyperlinkEvent);
  1191. } else {
  1192. var slideIndex = this.script.slideIndexFromSceneIndexLookup[sceneIndex];
  1193. var slideId = this.script.slideList[slideIndex];
  1194. var kpfEvent = new KPFEvent({
  1195. "slideId": slideId,
  1196. "slideIndex": slideIndex,
  1197. "sceneIndex": sceneIndex,
  1198. "event": this.script.events[sceneIndex],
  1199. "animationSupported": this.animationSupported
  1200. });
  1201. this.playbackController.renderEvent(kpfEvent);
  1202. }
  1203. this.updateNavigationButtons();
  1204. },
  1205. setCurrentSceneIndexTo: function(sceneIndex) {
  1206. this.currentSceneIndex = sceneIndex;
  1207. this.assignNextSceneIndex();
  1208. this.updateSlideNumber();
  1209. this.updateNavigationButtons();
  1210. },
  1211. assignNextSceneIndex: function() {
  1212. this.nextSceneIndex = this.calculateNextSceneIndex(this.currentSceneIndex);
  1213. },
  1214. calculateNextSceneIndex: function(sceneIndex) {
  1215. var nextSceneIndex = this.calculateNextSceneIndex_internal(sceneIndex);
  1216. return nextSceneIndex;
  1217. },
  1218. calculateNextSceneIndex_internal: function(sceneIndex) {
  1219. var nextSceneIndex = -1;
  1220. if (sceneIndex < this.script.lastSceneIndex) {
  1221. nextSceneIndex = sceneIndex + 1;
  1222. } else {
  1223. if (this.script.loopSlideshow) {
  1224. nextSceneIndex = 0;
  1225. } else {
  1226. nextSceneIndex = -1;
  1227. }
  1228. }
  1229. return nextSceneIndex;
  1230. },
  1231. updateSlideNumber: function() {
  1232. var adjustedSceneIndex = this.currentSceneIndex;
  1233. if (this.state === kShowControllerState_IdleAtFinalState) {
  1234. // because we're waiting at end state, we need to add one...
  1235. adjustedSceneIndex = this.nextSceneIndex;
  1236. }
  1237. var newSlideIndex = this.scriptManager.slideIndexFromSceneIndex(adjustedSceneIndex);
  1238. if (this.firstSlide) {
  1239. this.displayManager.hideWaitingIndicator();
  1240. runInNextEventLoop((function() {
  1241. this.startSoundTrack();
  1242. this.displayManager.clearLaunchMode();
  1243. }).bind(this));
  1244. this.firstSlide = false;
  1245. }
  1246. if (this.currentSlideIndex != newSlideIndex) {
  1247. this.previousSlideIndex = this.currentSlideIndex;
  1248. this.currentSlideIndex = newSlideIndex;
  1249. this.delegate.propertyChanged(kPropertyName_currentSlide, this.currentSlideIndex + 1);
  1250. // fire SlideIndexDidChangeEvent
  1251. document.fire(kSlideIndexDidChangeEvent, {
  1252. slideIndex: this.currentSlideIndex
  1253. });
  1254. }
  1255. },
  1256. updateNavigationButtons: function() {
  1257. var sceneIndexToUse = this.currentSceneIndex;
  1258. if (this.state === kShowControllerState_IdleAtFinalState) {
  1259. sceneIndexToUse++;
  1260. }
  1261. this.updateWindowHistory(sceneIndexToUse);
  1262. var enableBackwardButton = false;
  1263. var enableForwardButton = false
  1264. if (this.script.lastSceneIndex === -1) {
  1265. // this slideshow has only 1 slide with no builds, both buttons are
  1266. // disabled
  1267. enableForwardButton = false;
  1268. enableBackwardButton = false;
  1269. } else if (this.script.loopSlideshow) {
  1270. // this is a looping slideshow, both buttons are ALWAYS enabled
  1271. enableForwardButton = true;
  1272. enableBackwardButton = true;
  1273. } else {
  1274. if (sceneIndexToUse > 0) {
  1275. // sceneIndexToUse > 0, so enable backward button
  1276. enableBackwardButton = true;
  1277. }
  1278. if (sceneIndexToUse === 0 && this.script.lastSceneIndex === 0) {
  1279. // sceneIndexToUse & lastSceneIndex are both 0 - show with 1
  1280. // slide with 1 build, so enable forward button
  1281. enableForwardButton = true;
  1282. } else if (this.currentSceneIndex < this.script.lastSceneIndex) {
  1283. // currentSceneIndex < lastSceneIndex, so enable forward button
  1284. enableForwardButton = true;
  1285. } else if (this.currentSceneIndex === this.script.lastSceneIndex) {
  1286. if (this.state === kShowControllerState_IdleAtInitialState) {
  1287. // currentSceneIndex === lastSceneIndex, but we're at the
  1288. // intitial state, so enable forward button
  1289. enableForwardButton = true;
  1290. } else {
  1291. // currentSceneIndex === lastSceneIndex, and we're at the
  1292. // final state, so disable forward button
  1293. enableForwardButton = false;
  1294. }
  1295. } else {
  1296. // currentSceneIndex > lastSceneIndex, show with 1 slide and no
  1297. // builds, so disable forward button
  1298. enableForwardButton = false;
  1299. }
  1300. }
  1301. },
  1302. playCurrentScene: function(hyperlinkEventInfo) {
  1303. var previousState = this.state;
  1304. var sceneIndexToJump;
  1305. var delay = 0;
  1306. var duration = this.playbackController.eventOverallEndTime();
  1307. this.changeState(kShowControllerState_Playing);
  1308. this.clearAllHyperlinks();
  1309. if (this.helpPlacard.isShowing) {
  1310. this.helpPlacard.hide();
  1311. }
  1312. if (this.slideNumberDisplay.isShowing) {
  1313. this.slideNumberDisplay.hide();
  1314. }
  1315. if (hyperlinkEventInfo) {
  1316. sceneIndexToJump = hyperlinkEventInfo.sceneIndexToJump;
  1317. // clean up media cache if we are playing hyperlink transition
  1318. this.resetMediaCache();
  1319. } else {
  1320. sceneIndexToJump = this.nextSceneIndex;
  1321. // clean up media cache if we are advancing to different slide
  1322. var outgoingSlideIndex = this.scriptManager.slideIndexFromSceneIndex(this.currentSceneIndex);
  1323. var incomingSlideIndex = this.scriptManager.slideIndexFromSceneIndex(sceneIndexToJump);
  1324. if (outgoingSlideIndex !== incomingSlideIndex) {
  1325. this.resetMediaCache();
  1326. }
  1327. // for transition, the delay time is no longer coded into animations.
  1328. // set delay time and duration for automaticPlay transition
  1329. if (this.playbackController.kpfEvent.event.automaticPlay == true && this.playbackController.kpfEvent.event.effects[0].type === "transition") {
  1330. delay = this.playbackController.kpfEvent.event.effects[0].beginTime;
  1331. duration = this.playbackController.kpfEvent.event.effects[0].duration;
  1332. }
  1333. }
  1334. if (this.animationSupported) {
  1335. // animate events
  1336. // Chrome chokes up in some animations if starting animations immediately after layer drawings all on the main thread
  1337. // Start animations in a background thread will improve performance
  1338. // see <rdar://problem/12636430> DEMO: Chrome performance issues with cube, flip, push
  1339. clearTimeout(this.animateTimeout);
  1340. var effects = this.playbackController.kpfEvent.event.effects;
  1341. // if the event has no effects then no need to render effects
  1342. if (effects.length === 0) {
  1343. this.animateTimeout = setTimeout((function(){
  1344. setTimeout(this.currentSceneDidComplete.bind(this, sceneIndexToJump), duration * 1000 + 100);
  1345. }).bind(this), delay * 1000);
  1346. } else {
  1347. var renderedEffects;
  1348. // for performance consideration, render the effects as soon as we start playing so the effects are prepared and ready to animate
  1349. // for blinds there is an issue with incoming particles on top of ougoing particles
  1350. // since we don't want to show incoming particles before the animations start, we will render the effect later after delay as an exception case
  1351. if (effects[0].type === "transition") {
  1352. if (isIE || isEdge) {
  1353. // Blinds fallback to dissolve on IE so ok to render here without seeing particle in reverse order
  1354. renderedEffects = this.playbackController.renderEffects();
  1355. } else {
  1356. if (effects[0].name != "com.apple.iWork.Keynote.BLTBlinds") {
  1357. renderedEffects = this.playbackController.renderEffects();
  1358. }
  1359. }
  1360. }
  1361. // account for transition delay and start animating the effects after transition delay
  1362. this.animateTimeout = setTimeout((function(renderedEffects){
  1363. if (renderedEffects == null) {
  1364. renderedEffects = this.playbackController.renderEffects();
  1365. }
  1366. this.playbackController.animateEffects(renderedEffects);
  1367. setTimeout(this.currentSceneDidComplete.bind(this, sceneIndexToJump), duration * 1000 + 100);
  1368. }).bind(this, renderedEffects), delay * 1000);
  1369. }
  1370. } else {
  1371. var automatic = this.script.events[this.currentSceneIndex].automaticPlay;
  1372. if (sceneIndexToJump === -1) {
  1373. this.updateNavigationButtons();
  1374. if (this.delegate.getKPFJsonStringForShow) {
  1375. if (automatic) {
  1376. setTimeout(this.exitShow.bind(this), 2000);
  1377. } else {
  1378. this.exitShow();
  1379. }
  1380. } else {
  1381. this.changeState(kShowControllerState_IdleAtInitialState);
  1382. }
  1383. } else {
  1384. // For IE9, animate current event means jump to next slide since there is no animated end state
  1385. if (automatic) {
  1386. // For IE9, do a flat 2 sec delay for any automatic event to jump to next scene
  1387. setTimeout(
  1388. (function(){
  1389. this.changeState(kShowControllerState_IdleAtInitialState);
  1390. this.jumpToScene(sceneIndexToJump, this.script.events[sceneIndexToJump].automaticPlay);
  1391. }).bind(this), 2000);
  1392. } else {
  1393. // if it's not automatic then jump to next scene
  1394. this.changeState(kShowControllerState_IdleAtInitialState);
  1395. setTimeout(this.jumpToScene.bind(this, sceneIndexToJump, this.script.events[sceneIndexToJump].automaticPlay), 100);
  1396. }
  1397. }
  1398. }
  1399. },
  1400. currentSceneDidComplete: function(sceneIndexToJump) {
  1401. var script = this.script;
  1402. var showMode = script.showMode;
  1403. // hide slide number display after playing current scene
  1404. if (this.slideNumberDisplay.isShowing) {
  1405. this.slideNumberDisplay.hide();
  1406. }
  1407. // change state to final after animation completed
  1408. this.changeState(kShowControllerState_IdleAtFinalState);
  1409. if (showMode == kShowModeHyperlinksOnly || (sceneIndexToJump != -1 && sceneIndexToJump != this.nextSceneIndex)) {
  1410. // if the show mode is hyperlink only
  1411. // or if we play hyperlink transition that jumps to a slide which is not its next slide
  1412. // then jump to the slide after transition
  1413. var event = script.events[sceneIndexToJump];
  1414. var automaticPlay = event.automaticPlay == 1 || event.automaticPlay == true;
  1415. this.jumpToScene(sceneIndexToJump, automaticPlay);
  1416. } else if (this.nextSceneIndex === -1) {
  1417. // if next index is -1
  1418. this.updateNavigationButtons();
  1419. if (this.delegate.getKPFJsonStringForShow) {
  1420. this.stopSoundTrack();
  1421. this.exitShow();
  1422. } else {
  1423. this.stopSoundTrack();
  1424. }
  1425. } else if (script.events[this.nextSceneIndex].automaticPlay || showMode === kShowModeAutoplay) {
  1426. // invoking advanceToNextBuild() on next event loop
  1427. runInNextEventLoop(this.advanceToNextBuild.bind(this, "currentSceneDidComplete"));
  1428. }
  1429. },
  1430. resetMediaCache: function() {
  1431. this.resetMovieCache();
  1432. this.resetAudioCache();
  1433. },
  1434. resetMovieCache: function() {
  1435. for (var movieId in this.movieCache) {
  1436. delete this.movieCache[movieId].videoElement;
  1437. delete this.movieCache[movieId];
  1438. }
  1439. this.movieCache = null;
  1440. },
  1441. resetAudioCache: function() {
  1442. for (var audioId in this.audioCache) {
  1443. this.audioCache[audioId].pause();
  1444. this.audioCache[audioId].src = "";
  1445. delete this.audioCache[audioId];
  1446. }
  1447. this.audioCache = null;
  1448. },
  1449. updateWindowHistory: function(sceneIndex) {
  1450. // update url
  1451. if (typeof(window.history.replaceState) != "undefined") {
  1452. var currentUrl = document.URL.split("?");
  1453. var fragments = currentUrl[0].split("#");
  1454. if (window.location.protocol !== "file:") {
  1455. window.history.replaceState(null, "Keynote", fragments[0] + "#" + sceneIndex + (currentUrl[1] ? "?" + currentUrl[1] : ""));
  1456. }
  1457. }
  1458. },
  1459. startSoundTrack: function() {
  1460. if (this.script.soundtrack == null) {
  1461. return;
  1462. }
  1463. if (this.script.soundtrack.tracks == null) {
  1464. return;
  1465. }
  1466. if (this.script.soundtrack.mode === kSoundTrackModeOff) {
  1467. return;
  1468. }
  1469. this.currentSoundTrackIndex = 0;
  1470. this.playNextItemInSoundTrack();
  1471. },
  1472. stopSoundTrack: function() {
  1473. if (this.soundTrackPlayer) {
  1474. this.soundTrackPlayer.stopObserving("ended");
  1475. this.soundTrackPlayer.pause();
  1476. this.soundTrackPlayer = null;
  1477. }
  1478. },
  1479. playNextItemInSoundTrack: function() {
  1480. var soundtrackUrl = this.script.soundtrack.tracks[this.currentSoundTrackIndex];
  1481. this.soundTrackPlayer = new Audio();
  1482. this.soundTrackPlayer.src = "../" + soundtrackUrl;
  1483. this.soundTrackPlayer.volume = this.script.soundtrack.volume;
  1484. this.soundTrackPlayer.observe("ended", this.soundTrackItemDidComplete.bind(this), false);
  1485. this.soundTrackPlayer.load();
  1486. this.soundTrackPlayer.play();
  1487. },
  1488. soundTrackItemDidComplete: function() {
  1489. // check to see if there's anything else to play
  1490. this.currentSoundTrackIndex++;
  1491. if (this.currentSoundTrackIndex < this.script.soundtrack.tracks.length) {
  1492. this.playNextItemInSoundTrack();
  1493. } else {
  1494. if (this.script.soundtrack.mode === kSoundTrackModePlayOnce) {
  1495. this.soundTrackPlayer = null;
  1496. } else if (this.script.soundtrack.mode === kSoundTrackModeLooping) {
  1497. // nope, but we're in loop mode so take it from the top
  1498. this.startSoundTrack();
  1499. }
  1500. }
  1501. },
  1502. processHyperlink: function(hyperlink) {
  1503. var hyperlinkUrl = hyperlink.url;
  1504. var hyperlinkEffect;
  1505. // perform hyperlink jump
  1506. if (hyperlinkUrl.indexOf("?slide=") === 0) {
  1507. var key = hyperlinkUrl.substring(7);
  1508. var newSlideIndex = -1;
  1509. if (key === "first") {
  1510. newSlideIndex = 0;
  1511. } else if (key === "last") {
  1512. newSlideIndex = this.script.slideCount - 1;
  1513. } else {
  1514. var sceneIndexToUse = this.currentSceneIndex;
  1515. var nextSlideIndex = -1;
  1516. switch (this.state) {
  1517. case kShowControllerState_IdleAtFinalState:
  1518. sceneIndexToUse = sceneIndexToUse + 1;
  1519. case kShowControllerState_IdleAtInitialState:
  1520. var currentSlideIndex = this.scriptManager.slideIndexFromSceneIndex(sceneIndexToUse);
  1521. if (key === "next") {
  1522. if (currentSlideIndex === this.script.slideCount - 1) {
  1523. if (this.script.loopSlideshow) {
  1524. nextSlideIndex = 0;
  1525. } else {
  1526. if (this.delegate.getKPFJsonStringForShow) {
  1527. this.exitShow();
  1528. }
  1529. }
  1530. } else {
  1531. nextSlideIndex = currentSlideIndex + 1;
  1532. }
  1533. } else if (key === "previous") {
  1534. if (currentSlideIndex === 0) {
  1535. if (this.script.loopSlideshow) {
  1536. nextSlideIndex = this.script.slideCount - 1;
  1537. } else {
  1538. nextSlideIndex = 0;
  1539. }
  1540. } else {
  1541. nextSlideIndex = currentSlideIndex - 1;
  1542. }
  1543. }
  1544. break;
  1545. default:
  1546. break;
  1547. }
  1548. newSlideIndex = nextSlideIndex;
  1549. }
  1550. if (newSlideIndex != -1) {
  1551. this.jumpToHyperlinkSlide(newSlideIndex, hyperlink);
  1552. }
  1553. } else if (hyperlinkUrl.indexOf("?slideid=") === 0) {
  1554. // find by slideId
  1555. var slideId = hyperlinkUrl.substring(9);
  1556. var slideList = this.script.slideList;
  1557. var newSlideIndex = -1;
  1558. for (var i = 0, length = slideList.length; i < length; i++) {
  1559. if (slideList[i] === slideId) {
  1560. newSlideIndex = i;
  1561. break;
  1562. }
  1563. }
  1564. if (newSlideIndex != -1) {
  1565. this.jumpToHyperlinkSlide(newSlideIndex, hyperlink);
  1566. }
  1567. } else if (hyperlinkUrl.indexOf("?action=retreat") === 0) {
  1568. // jump to the last slide viewed
  1569. if (this.lastSlideViewedIndex != -1) {
  1570. this.jumpToHyperlinkSlide(this.lastSlideViewedIndex, hyperlink);
  1571. }
  1572. } else if (hyperlinkUrl.indexOf("?action=exitpresentation") === 0) {
  1573. // exit show
  1574. this.exitShow();
  1575. } else if (hyperlinkUrl.indexOf("http:") === 0 || hyperlinkUrl.indexOf("https:") === 0) {
  1576. // jump to a web page
  1577. window.open(hyperlinkUrl, "_blank", null);
  1578. } else if (hyperlinkUrl.indexOf("mailto:") === 0) {
  1579. // email link
  1580. window.location = hyperlinkUrl;
  1581. }
  1582. },
  1583. jumpToHyperlinkSlide: function(slideIndexToJump, hyperlink) {
  1584. //var hyperlinkEffects = hyperlink.effects;
  1585. var hyperlinkEvents = hyperlink.events;
  1586. var sceneIndexToJump = this.script.sceneIndexFromSlideIndexLookup[slideIndexToJump];
  1587. if (hyperlinkEvents) {
  1588. // if hyperlink has effect then display scene and use hyperlink event
  1589. var slideIdToJump = this.script.slideList[slideIndexToJump];
  1590. var hyperlinkEvent = hyperlinkEvents[slideIdToJump];
  1591. if (hyperlinkEvent) {
  1592. var sceneIndexOfHyperlink = this.currentSceneIndex;
  1593. switch (this.state) {
  1594. case kShowControllerState_IdleAtFinalState:
  1595. if (sceneIndexOfHyperlink < this.script.numScenes - 1) {
  1596. sceneIndexOfHyperlink = sceneIndexOfHyperlink + 1;
  1597. } else {
  1598. if (this.script.loopSlideshow) {
  1599. sceneIndexOfHyperlink = 0;
  1600. }
  1601. }
  1602. case kShowControllerState_IdleAtInitialState:
  1603. var slideIndexOfHyperlink = this.script.slideIndexFromSceneIndexLookup[sceneIndexOfHyperlink];
  1604. var slideIdOfHyperlink = this.script.slideList[slideIndexOfHyperlink];
  1605. var kpfEvent = new KPFEvent({
  1606. "slideId": slideIdOfHyperlink,
  1607. "slideIndex": slideIndexOfHyperlink,
  1608. "sceneIndex": sceneIndexOfHyperlink,
  1609. "event": hyperlinkEvent,
  1610. "animationSupported": this.animationSupported
  1611. });
  1612. // display scene that contains hyperlink and use hyperlink event
  1613. this.displayScene(sceneIndexOfHyperlink, kpfEvent);
  1614. // play current hyperlink scene
  1615. this.playCurrentScene({"sceneIndexToJump": sceneIndexToJump});
  1616. break;
  1617. default:
  1618. return;
  1619. }
  1620. } else {
  1621. // if no hyperlink effect, just jump to slide
  1622. var event = this.script.events[sceneIndexToJump];
  1623. var automaticPlay = event.automaticPlay == 1 || event.automaticPlay == true;
  1624. this.jumpToSlide(slideIndexToJump + 1, automaticPlay);
  1625. }
  1626. } else {
  1627. // if no hyperlink effect, just jump to slide
  1628. var event = this.script.events[sceneIndexToJump];
  1629. var automaticPlay = event.automaticPlay == 1 || event.automaticPlay == true;
  1630. this.jumpToSlide(slideIndexToJump + 1, automaticPlay);
  1631. }
  1632. },
  1633. addMovieHyperlink: function(targetRectangle, url) {
  1634. var newHyperlink = {
  1635. targetRectangle: targetRectangle,
  1636. url: url
  1637. };
  1638. this.movieHyperlinks.push(newHyperlink);
  1639. },
  1640. clearMovieHyperlinks: function() {
  1641. delete this.movieHyperlinks;
  1642. this.movieHyperlinks = new Array();
  1643. },
  1644. clearAllHyperlinks: function() {
  1645. this.stageManager.clearAllHyperlinks();
  1646. delete this.activeHyperlinks;
  1647. this.activeHyperlinks = new Array();
  1648. },
  1649. findHyperlinkAtCoOrds: function(showCoOrds) {
  1650. var numHyperlinks = this.activeHyperlinks != null ? this.activeHyperlinks.length : 0;
  1651. for (var i = numHyperlinks; i > 0; i--) {
  1652. var hyperlink = this.activeHyperlinks[i - 1];
  1653. var hyperlinkRect = hyperlink.targetRectangle;
  1654. hyperlinkLeft = Math.floor(hyperlinkRect.x);
  1655. hyperlinkTop = Math.floor(hyperlinkRect.y);
  1656. hyperlinkRight = hyperlinkLeft + Math.floor(hyperlinkRect.width);
  1657. hyperlinkBottom = hyperlinkTop + Math.floor(hyperlinkRect.height);
  1658. if ((showCoOrds.pointX >= hyperlinkLeft) && (showCoOrds.pointX <= hyperlinkRight)
  1659. && (showCoOrds.pointY >= hyperlinkTop) && (showCoOrds.pointY <= hyperlinkBottom)) {
  1660. return hyperlink;
  1661. }
  1662. }
  1663. return null;
  1664. },
  1665. createHyperlinksForCurrentState: function(context) {
  1666. var sceneIndexOfHyperlinks = -1;
  1667. switch (this.state) {
  1668. case kShowControllerState_IdleAtInitialState:
  1669. // use current scene index
  1670. sceneIndexOfHyperlinks = this.currentSceneIndex;
  1671. break;
  1672. case kShowControllerState_IdleAtFinalState:
  1673. // idle at final state, grab hyperlink from appropriate scene
  1674. if (this.currentSceneIndex < this.script.lastSceneIndex) {
  1675. sceneIndexOfHyperlinks = this.currentSceneIndex + 1;
  1676. } else {
  1677. // check if hyperlinks only mode
  1678. if (this.script.showMode == kShowModeHyperlinksOnly) {
  1679. sceneIndexOfHyperlinks = this.currentSceneIndex;
  1680. } else {
  1681. // check if loop slide show
  1682. if (this.script.loopSlideshow) {
  1683. sceneIndexOfHyperlinks = 0;
  1684. }
  1685. }
  1686. }
  1687. break;
  1688. default:
  1689. break;
  1690. }
  1691. if (sceneIndexOfHyperlinks != -1) {
  1692. this.clearAllHyperlinks();
  1693. this.createHyperlinks(sceneIndexOfHyperlinks);
  1694. }
  1695. },
  1696. createHyperlinks: function(hyperlinkSceneIndex) {
  1697. if (hyperlinkSceneIndex === -1) {
  1698. return;
  1699. }
  1700. var eventTimeLine = this.script.events[hyperlinkSceneIndex];
  1701. if (eventTimeLine == null) {
  1702. return;
  1703. }
  1704. var hyperlinks = eventTimeLine.hyperlinks;
  1705. if (hyperlinks == null) {
  1706. return;
  1707. }
  1708. var numHyperlinks = hyperlinks.length;
  1709. var iHyperlink;
  1710. var kMinHyperlinkWidth = 150;
  1711. var kMinHyperlinkHeight = 50;
  1712. var showWidth = this.displayManager.showWidth;
  1713. var showHeight = this.displayManager.showHeight;
  1714. for (iHyperlink = 0; iHyperlink < numHyperlinks; iHyperlink++) {
  1715. var hyperlink = hyperlinks[iHyperlink];
  1716. var hyperlinkRect = hyperlink.targetRectangle;
  1717. var activeHyperlink = {
  1718. targetRectangle: hyperlinkRect,
  1719. events: hyperlink.events,
  1720. url: hyperlink.url
  1721. };
  1722. var spaceOnLeft = hyperlinkRect.x;
  1723. var spaceOnTop = hyperlinkRect.y;
  1724. var spaceOnRight = showWidth - (hyperlinkRect.x + hyperlinkRect.width);
  1725. var spaceOnBottom = showHeight - (hyperlinkRect.y + hyperlinkRect.top);
  1726. this.stageManager.addHyperlink(activeHyperlink.targetRectangle);
  1727. this.activeHyperlinks[iHyperlink] = activeHyperlink;
  1728. }
  1729. if (this.movieHyperlinks.length > 0) {
  1730. for (var iMovieHyperlink = 0; iMovieHyperlink < this.movieHyperlinks.length; iMovieHyperlink++) {
  1731. var movieHyperlink = this.movieHyperlinks[iMovieHyperlink];
  1732. this.stageManager.addHyperlink(movieHyperlink.targetRectangle);
  1733. this.activeHyperlinks[iHyperlink++] = movieHyperlink;
  1734. }
  1735. }
  1736. }
  1737. });