From 8521eb36ba0ce40018a94133e0fb22241882e5eb Mon Sep 17 00:00:00 2001 From: rlx <0x0073@0x2620.org> Date: Wed, 14 Mar 2012 10:35:46 +0000 Subject: [PATCH] add video timeline player and demo --- demos/videotimelineplayer/index.html | 11 + demos/videotimelineplayer/js/demo.js | 77 ++ demos/videotimelineplayer/srt/0097514.srt | 710 ++++++++++++++++++ .../Ox.UI/js/Video/Ox.BlockVideoTimeline.js | 31 +- .../Ox.UI/js/Video/Ox.VideoTimelinePlayer.js | 532 +++++++++++++ 5 files changed, 1347 insertions(+), 14 deletions(-) create mode 100644 demos/videotimelineplayer/index.html create mode 100644 demos/videotimelineplayer/js/demo.js create mode 100644 demos/videotimelineplayer/srt/0097514.srt create mode 100644 source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js diff --git a/demos/videotimelineplayer/index.html b/demos/videotimelineplayer/index.html new file mode 100644 index 00000000..57f6360e --- /dev/null +++ b/demos/videotimelineplayer/index.html @@ -0,0 +1,11 @@ + + + + OxJS Video Timeline Player Demo + + + + + + \ No newline at end of file diff --git a/demos/videotimelineplayer/js/demo.js b/demos/videotimelineplayer/js/demo.js new file mode 100644 index 00000000..5eafafab --- /dev/null +++ b/demos/videotimelineplayer/js/demo.js @@ -0,0 +1,77 @@ +Ox.load('UI', function() { + + Ox.Theme('modern'); + + var $innerPanel = Ox.SplitPanel({ + elements: [ + { + element: Ox.Element(), + resizable: true, + resize: [64, 128, 192], + size: 64 + }, + { + element: Ox.Element() + } + ], + orientation: 'vertical' + }), + $outerPanel = Ox.SplitPanel({ + elements: [ + { + element: Ox.Element(), + resizable: true, + resize: [128, 256, 384], + size: 128 + }, + { + element: $innerPanel + } + ], + orientation: 'horizontal' + }).appendTo(Ox.$body); + + Ox.get('srt/0097514.srt', function(srt) { + + var $videoTimelinePlayer = Ox.VideoTimelinePlayer({ + duration: 3029.96, + getFrameURL: function(position) { + return 'http://0xdb.org/0097514/64p' + position + '.jpg' + }, + getImageURL: function(i) { + return 'http://0xdb.org/0097514/timeline64p' + i + '.png' + }, + height: window.innerHeight - 65, + paused: true, + position: 1000, + subtitles: Ox.parseSRT(srt), + timelineURL: 'http://0xdb.org/0097514/timeline16p.png', + videoRatio: 1.291667, + videoURL: 'http://0xdb.org/0097514/96p.webm', + width: window.innerWidth - 129 + }) + .bindEvent({ + resizeend: function(data) { + Ox.print('RESIZE', data.size) + $videoTimelinePlayer.options({ + height: data.size + }); + } + }); + + $innerPanel + .replaceElement(1, $videoTimelinePlayer) + .bindEvent({ + resize: function(data) { + Ox.print('RESIZE', data.size) + $videoTimelinePlayer.options({ + width: data.size + }); + } + }); + + $videoTimelinePlayer.gainFocus(); + + }); + +}); diff --git a/demos/videotimelineplayer/srt/0097514.srt b/demos/videotimelineplayer/srt/0097514.srt new file mode 100644 index 00000000..c6c15af9 --- /dev/null +++ b/demos/videotimelineplayer/srt/0097514.srt @@ -0,0 +1,710 @@ +1 +00:00:00,680 --> 00:00:04,389 +Change nothing +so that everything is different. + +2 +00:00:24,560 --> 00:00:27,120 +Don't show every side of things. + +3 +00:00:27,680 --> 00:00:30,672 +Allow yourself +a margin of indefiniteness. + +4 +00:05:11,960 --> 00:05:16,272 +Histories of cinema, +in the plural. + +5 +00:05:27,000 --> 00:05:29,719 +All the stories + +6 +00:05:30,680 --> 00:05:33,240 +that will be + +7 +00:05:34,560 --> 00:05:36,118 +that have been. + +8 +00:09:06,760 --> 00:09:10,878 +To tell the story of the last tycoon + +9 +00:09:12,480 --> 00:09:13,754 +Irving Thalberg. + +10 +00:09:17,000 --> 00:09:21,835 +A TV executive has, at most, +200 films a year in his head. + +11 +00:09:23,160 --> 00:09:25,799 +Thalberg was the only person who, + +12 +00:09:26,000 --> 00:09:28,958 +every day, had 52 films in his head. + +13 +00:10:50,440 --> 00:10:51,839 +The foundation. + +14 +00:10:52,880 --> 00:10:54,598 +The founding father. + +15 +00:11:02,720 --> 00:11:04,119 +The only son. + +16 +00:11:10,200 --> 00:11:12,839 +This story had to pass by this: + +17 +00:11:14,960 --> 00:11:17,872 +A young body, +fragile and beautiful, + +18 +00:11:29,160 --> 00:11:31,674 +as described +F. Scott Fitzgerald - + +19 +00:11:32,000 --> 00:11:35,072 +so that this could exist: + +20 +00:11:38,160 --> 00:11:39,752 +The power of Hollywood. + +21 +00:13:22,800 --> 00:13:25,075 +The power of Babylon. + +22 +00:13:38,480 --> 00:13:39,879 +A dream factory. + +23 +00:13:43,320 --> 00:13:45,231 +History of cinema. + +24 +00:13:48,080 --> 00:13:51,470 +Newness of history. +History of news. + +25 +00:15:59,920 --> 00:16:03,799 +What has gone through cinema +and is still marked by it + +26 +00:16:04,000 --> 00:16:06,150 +can no longer enter anywhere else. + +27 +00:16:10,240 --> 00:16:12,196 +A dream factory. + +28 +00:16:22,800 --> 00:16:27,112 +Communism wore itself out +trying to dream up such factories. + +29 +00:17:46,520 --> 00:17:50,638 +Married to one of +the prettiest women alive. + +30 +00:18:07,440 --> 00:18:10,034 +Or to tell the story of Howard Hughes. + +31 +00:18:35,720 --> 00:18:37,517 +Braver than Mermoz, + +32 +00:18:38,200 --> 00:18:39,792 +richer than Rockefeller. + +33 +00:19:13,400 --> 00:19:16,790 +Producer of Citizen Kane, +president of TWA. + +34 +00:19:17,800 --> 00:19:21,270 +As if Meliès ran Gallimard +and the SNCF. + +35 +00:19:55,680 --> 00:19:57,398 +And before Hughes Aircraft + +36 +00:19:57,600 --> 00:20:02,116 +began fishing up CIA submarines +from the bottom of the Pacific, + +37 +00:20:07,800 --> 00:20:11,793 +he forced RKO starlets +to go on weekly limousine rides + +38 +00:20:16,560 --> 00:20:18,039 +at one mile an hour + +39 +00:20:22,640 --> 00:20:26,110 +so their breasts +wouldn't bounce up and down. + +40 +00:20:35,640 --> 00:20:38,598 +His death, worse than anything + +41 +00:20:38,800 --> 00:20:41,519 +Defoe had imagined for Robinson. + +42 +00:23:06,840 --> 00:23:11,277 +To tell the stories +of all the films never made... + +43 +00:24:08,240 --> 00:24:12,791 +To tell the stories +of all the films never made... + +44 +00:25:52,200 --> 00:25:55,875 +To tell the stories +of all the films never made, + +45 +00:26:02,360 --> 00:26:04,271 +rather than those that were. + +46 +00:26:11,800 --> 00:26:14,268 +Those that were +can be seen on TV. + +47 +00:26:14,480 --> 00:26:16,675 +Let's not exaggerate: + +48 +00:26:16,880 --> 00:26:19,997 +They're not even +copies of reproductions. + +49 +00:27:19,800 --> 00:27:23,679 +1940. Geneva. +L'Ecole des femmes. Max Ophuls. + +50 +00:27:55,080 --> 00:27:57,719 +He fell on Madeleine Ozeray's ass, + +51 +00:27:57,920 --> 00:28:01,799 +while the Germans were taking +the French from behind + +52 +00:28:04,920 --> 00:28:07,718 +and while Louis Jouvet was giving up. + +53 +00:29:28,280 --> 00:29:31,078 +Theater is something +too much known. + +54 +00:29:32,200 --> 00:29:34,839 +The cinématographe, too unknown... + +55 +00:29:35,720 --> 00:29:36,675 +up to now. + +56 +00:31:29,680 --> 00:31:33,514 +History of cinema +newness of history. + +57 +00:31:33,720 --> 00:31:35,438 +History of news. + +58 +00:31:37,160 --> 00:31:40,914 +Histories of cinema, with "s". + +59 +00:31:41,520 --> 00:31:43,033 +With SS. + +60 +00:32:21,160 --> 00:32:23,310 +1939, 1940, 1941... + +61 +00:32:23,720 --> 00:32:25,472 +Betrayal by radio, + +62 +00:32:26,200 --> 00:32:27,872 +but cinema keeps its word. + +63 +00:32:37,160 --> 00:32:41,358 +Because from Siegfried and M +to the dictator and Lubitsch, + +64 +00:32:42,520 --> 00:32:43,873 +films were made. + +65 +00:32:48,960 --> 00:32:50,393 +1940, 1941. + +66 +00:32:51,240 --> 00:32:55,552 +Even scratched to death, +a simple 35-millimeter rectangle + +67 +00:32:56,080 --> 00:32:58,640 +saves the honor of reality. + +68 +00:33:00,160 --> 00:33:01,991 +1941, 1942. + +69 +00:33:02,480 --> 00:33:05,517 +If poor images still strike + +70 +00:33:05,920 --> 00:33:09,276 +without anger or hatred, +like a butcher, + +71 +00:33:10,280 --> 00:33:12,874 +it is because cinema is there: +Silent film, + +72 +00:33:13,080 --> 00:33:16,959 +with its humble and formidable +power of transfiguration. + +73 +00:33:19,080 --> 00:33:21,674 +1942, 1943, 1944. + +74 +00:33:23,040 --> 00:33:24,758 +That which plunges into the night + +75 +00:33:24,960 --> 00:33:27,793 +is the echo of what silence submerges. + +76 +00:33:29,960 --> 00:33:31,871 +What silence submerges + +77 +00:33:35,240 --> 00:33:38,915 +sustains in light +that which plunges into the night. + +78 +00:35:43,160 --> 00:35:44,991 +Images and sounds, + +79 +00:35:54,320 --> 00:35:56,993 +like travelers whose paths cross + +80 +00:35:57,200 --> 00:35:59,156 +and who can no longer part ways. + +81 +00:36:35,240 --> 00:36:37,993 +To prove it, the masses like myths. + +82 +00:36:38,200 --> 00:36:40,236 +Cinema speaks to the masses. + +83 +00:36:41,400 --> 00:36:43,709 +But if the myth begins with Fantomas, + +84 +00:36:43,920 --> 00:36:45,512 +it ends with Christ. + +85 +00:36:46,520 --> 00:36:49,751 +What did those who listened +to St. Bernard hear? + +86 +00:36:49,960 --> 00:36:51,996 +Not what he was saying? + +87 +00:36:52,200 --> 00:36:54,270 +Maybe. Probably. + +88 +00:36:55,920 --> 00:36:58,559 +How can we neglect what we learn + +89 +00:36:58,760 --> 00:37:02,548 +when that unknown voice +plunges deep into our hearts? + +90 +00:37:35,480 --> 00:37:37,914 +What the news can teach us: + +91 +00:37:38,840 --> 00:37:41,673 +The birth of a nation, +of hope, + +92 +00:37:48,760 --> 00:37:50,352 +of Rome, Open City. + +93 +00:37:50,560 --> 00:37:53,870 +The cinématographe +never meant to create an event, + +94 +00:37:54,080 --> 00:37:55,274 +but a vision. + +95 +00:38:10,680 --> 00:38:12,272 +Because the screen + +96 +00:38:12,480 --> 00:38:13,959 +is the same white canvas + +97 +00:38:14,160 --> 00:38:16,151 +as the Samaritan's shirt. + +98 +00:38:24,440 --> 00:38:28,149 +What Arnold and Richter's +cameras preserve, + +99 +00:38:28,840 --> 00:38:32,515 +so as not to be outdone +by nightmares and dreams, + +100 +00:38:33,080 --> 00:38:37,392 +will not be shown on a screen, +but on a shroud. + +101 +00:38:44,800 --> 00:38:47,109 +If the deaths of Puig and the Négus, + +102 +00:38:47,320 --> 00:38:49,311 +of Captain Boïeldieu, + +103 +00:38:49,760 --> 00:38:52,479 +and of the little bunny +were inaudible, + +104 +00:38:53,280 --> 00:38:56,955 +it is because life never rendered +what it stole from film. + +105 +00:38:58,360 --> 00:39:02,069 +Forgetting extermination +is part of extermination. + +106 +00:39:35,400 --> 00:39:36,879 +Histories of cinema, + +107 +00:39:37,680 --> 00:39:39,910 +stories without speech, + +108 +00:39:40,560 --> 00:39:42,710 +stories of the night. + +109 +00:40:12,040 --> 00:40:14,474 +For nearly 50 years, in the dark, + +110 +00:40:14,680 --> 00:40:19,151 +moviegoers burn imagination +to heat up reality. + +111 +00:40:19,760 --> 00:40:21,830 +Now reality is seeking revenge. + +112 +00:40:22,040 --> 00:40:24,873 +It wants real tears, real blood. + +113 +00:40:32,480 --> 00:40:33,674 +From Vienna to Madrid, + +114 +00:40:33,880 --> 00:40:35,108 +from Siodmak to Capra, + +115 +00:40:35,320 --> 00:40:37,675 +from Paris +to Los Angeles and Moscow, + +116 +00:40:37,880 --> 00:40:40,189 +from Renoir +to Malraux and Dovjenko - + +117 +00:40:40,400 --> 00:40:44,757 +great fiction directors +couldn't control the vengeance + +118 +00:40:45,520 --> 00:40:48,159 +which they had directed over and over. + +119 +00:42:05,120 --> 00:42:07,076 +The poor cinema of news + +120 +00:42:07,280 --> 00:42:10,431 +must clear the blood and tears +of all suspicion + +121 +00:42:10,640 --> 00:42:13,359 +as streets are cleaned too late, + +122 +00:42:13,880 --> 00:42:16,519 +after the army +has fired at the masses. + +123 +00:42:34,320 --> 00:42:35,673 +What there is of cinema + +124 +00:42:35,880 --> 00:42:38,678 +in wartime newsreels +says nothing. + +125 +00:42:38,880 --> 00:42:40,393 +It does not judge. + +126 +00:42:53,520 --> 00:42:55,351 +No close-ups. + +127 +00:42:55,560 --> 00:42:57,437 +Suffering is not a star. + +128 +00:43:02,080 --> 00:43:06,392 +Nor is the burned-down church +or the bombed-out countryside. + +129 +00:44:02,600 --> 00:44:06,388 +The spirits of Flaherty and Epstein +took over. + +130 +00:44:06,600 --> 00:44:10,878 +Daumier and Rembrandt, +with his terrifying black and white. + +131 +00:44:52,600 --> 00:44:54,033 +A few pans, + +132 +00:44:54,240 --> 00:44:59,075 +an occasional high angle, for a mother +mourning her murdered child. + +133 +00:45:45,400 --> 00:45:48,153 +It is because this time alone + +134 +00:45:48,840 --> 00:45:53,197 +the only veritable popular art form +rejoins painting, + +135 +00:45:53,400 --> 00:45:55,118 +that is: Art. + +136 +00:45:55,320 --> 00:45:58,392 +That is: What is reborn +from what was burned. + +137 +00:46:26,640 --> 00:46:30,474 +We've forgotten that village, +its white walls and olive trees, + +138 +00:46:30,680 --> 00:46:32,432 +but we remember Picasso, + +139 +00:46:32,920 --> 00:46:34,558 +that is: Guernica. + +140 +00:46:44,120 --> 00:46:48,511 +We've forgotten Valentin Feldman, +killed in '43, + +141 +00:46:49,320 --> 00:46:52,437 +but we remember +at least one other prisoner, + +142 +00:46:52,640 --> 00:46:54,119 +that is: Goya. + +143 +00:46:55,160 --> 00:46:59,915 +And if George Stevens hadn't used +the first 16-mm color film + +144 +00:47:00,280 --> 00:47:02,396 +in Auschwitz and Ravensbruck, + +145 +00:47:04,040 --> 00:47:08,192 +Elizabeth Taylor would never +have found a place in the sun. + +146 +00:47:18,920 --> 00:47:20,638 +1939, 1944. + +147 +00:47:21,760 --> 00:47:24,832 +Martyrdom and resurrection +of the documentary. + +148 +00:47:28,600 --> 00:47:32,673 +How marvelous to be able to look at +what we cannot see. + +149 +00:47:33,040 --> 00:47:35,918 +What a miracle for our blind eyes. + +150 +00:47:41,600 --> 00:47:42,749 +Besides that, + +151 +00:47:42,960 --> 00:47:44,552 +cinema is an industry. + +152 +00:47:45,000 --> 00:47:47,560 +And if World War I allowed + +153 +00:47:47,760 --> 00:47:50,228 +American cinema +to ruin French cinema, + +154 +00:47:50,680 --> 00:47:52,352 +with the advent of television, + +155 +00:47:52,560 --> 00:47:54,437 +World War II allowed it to finance, + +156 +00:47:55,120 --> 00:47:57,395 +that is, to ruin, European cinema. + +157 +00:48:15,200 --> 00:48:17,634 +"Do you have two hands?" +Asks the blind man. + +158 +00:48:28,320 --> 00:48:31,232 +But looking won't reassure me. + +159 +00:48:31,960 --> 00:48:35,430 +Why trust my eyes, if I have doubts? + +160 +00:48:36,120 --> 00:48:38,793 +Why check my eyes + +161 +00:48:39,000 --> 00:48:41,355 +to see whether I see my hands? + +162 +00:49:56,880 --> 00:49:58,518 +Help! + diff --git a/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js b/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js index c9a5b6f5..c2822b88 100644 --- a/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js +++ b/source/Ox.UI/js/Video/Ox.BlockVideoTimeline.js @@ -40,10 +40,11 @@ Ox.BlockVideoTimeline = function(options, self) { self.$interfaces = []; self.$lines = []; self.$tooltip = Ox.Tooltip({ - animate: false - }).css({ - textAlign: 'center' - }); + animate: false + }) + .css({ + textAlign: 'center' + }); self.height = 16; self.lines = getLines(); self.margin = 8; @@ -93,7 +94,7 @@ Ox.BlockVideoTimeline = function(options, self) { self.$lines[i] = $('
') .css({ position: 'absolute', - left: (self.margin / 2) + 'px', + left: self.margin / 2 + 'px', top: i * (self.height + self.margin) + 'px', width: self.options.width + 'px', height: '24px', @@ -103,7 +104,7 @@ Ox.BlockVideoTimeline = function(options, self) { self.$images[i] = self.$image.clone() .css({ position: 'absolute', - marginLeft: (-i * self.options.width) + 'px' + marginLeft: -i * self.options.width + 'px' }) .appendTo(self.$lines[i]); self.$interfaces[i] = $('
') @@ -113,7 +114,7 @@ Ox.BlockVideoTimeline = function(options, self) { top: '2px', width: Math.round(self.options.duration) + 'px', height: '20px', - marginLeft: (-i * self.options.width) + 'px', + marginLeft: -i * self.options.width + 'px', //background: 'rgba(255, 0, 0, 0.1)', }) .appendTo(self.$lines[i]); @@ -141,7 +142,8 @@ Ox.BlockVideoTimeline = function(options, self) { function getPosition(e) { //FIXME: this might still be broken in opera according to http://acko.net/blog/mouse-handling-and-absolute-positions-in-javascript - return (e.offsetX ? e.offsetX : e.clientX - $(e.target).offset().left); + return e.offsetX ? e.offsetX + : e.clientX - $(e.target).offset().left; } function getSubtitle(position) { @@ -188,12 +190,13 @@ Ox.BlockVideoTimeline = function(options, self) { position = getPosition(e); subtitle = getSubtitle(position); self.$tooltip.options({ - title: subtitle ? - '' + - Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '
') + - '

' + - Ox.formatDuration(subtitle['in'], 3) + ' - ' + Ox.formatDuration(subtitle['out'], 3) : - Ox.formatDuration(position) + title: subtitle + ? '' + + Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '
') + + '

' + + Ox.formatDuration(subtitle['in'], 3) + ' - ' + + Ox.formatDuration(subtitle['out'], 3) + : Ox.formatDuration(position) }) .show(e.clientX, e.clientY); } else { diff --git a/source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js b/source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js new file mode 100644 index 00000000..e84fcefa --- /dev/null +++ b/source/Ox.UI/js/Video/Ox.VideoTimelinePlayer.js @@ -0,0 +1,532 @@ +// vim: et:ts=4:sw=4:sts=4:ft=javascript + +'use strict'; + +Ox.VideoTimelinePlayer = function(options, self) { + + self = self || {}; + var that = Ox.Element({}, self) + .defaults({ + cuts: [], + duration: 0, + find: '', + getFrameURL: null, + getImageURL: null, + height: 0, + 'in': 0, + matches: [], + out: 0, + paused: false, + position: 0, + subtitles: [], + timelineURL: '', + videoRatio: 1, + width: 0 + }) + .options(options || {}) + .addClass('OxVideoTimelinePlayer'); + + self.fps = 25; + self.frame = self.options.position * self.fps; + self.frames = self.options.duration * self.fps; + self.tileWidth = 1500; + self.tileHeight = 64; + self.margin = 8; + self.contentWidth = self.options.width - 2 * self.margin - Ox.UI.SCROLLBAR_SIZE; + self.contentHeight = self.options.height - 32; + self.tiles = Math.ceil(self.frames / self.tileWidth); + self.videoWidth = Math.round(self.tileHeight * self.options.videoRatio); + self.videoLines = getVideoLines(); + self.lines = getLines(); + + self.$topBar = Ox.Bar({size: 16}); + + self.$content = Ox.Element() + .addClass('OxVideoTimelinePlayer') + .css({ + overflow: 'hidden', + overflowY: 'scroll' + }) + .bind({ + mousedown: mousedown, + mouseleave: mouseleave, + mousemove: mousemove + }) + .bindEvent({ + mousedown: function() { + this.gainFocus(); + }, + key_down: function() { + self.options.position += self.contentWidth / self.fps; + setPosition(); + }, + key_enter: function() { + scrollToPosition(); + }, + key_left: function() { + self.options.position -= self.videoWidth / self.fps; + setPosition(); + }, + key_right: function() { + self.options.position += self.videoWidth / self.fps; + setPosition(); + }, + key_shift_left: function() { + self.options.position -= 1 / self.fps; + setPosition(); + }, + key_shift_right: function() { + self.options.position += 1 / self.fps; + setPosition(); + }, + key_space: togglePaused, + key_up: function() { + self.options.position -= self.contentWidth / self.fps; + setPosition(); + } + + }); + + self.$bottomBar = Ox.Bar({size: 16}); + self.$playButton = Ox.Button({ + style: 'symbol', + title: 'play', + type: 'image' + }) + .css({ + float: 'left' + }) + .bindEvent({ + click: togglePaused + }) + .appendTo(self.$bottomBar); + self.$muteButton = Ox.Button({ + style: 'symbol', + title: 'mute', + type: 'image' + }) + .css({ + float: 'left' + }) + .bindEvent({ + + }) + .appendTo(self.$bottomBar); + self.$smallTimeline = $('
') + .css({ + float: 'left', + width: self.options.width - 80 + 'px', + height: '16px', + borderRadius: '8px', + background: 'rgb(0, 0, 0)' + }) + .appendTo(self.$bottomBar); + self.$smallTimelineImage = $('') + .attr({ + src: self.options.timelineURL + }) + .css({ + width: self.options.width - 96 + 'px', + height: '16px', + margin: '0 8px 0 8px' + }) + .appendTo(self.$smallTimeline); + self.$position = $('
') + .css({ + float: 'left', + width: '40px', + height: '12px', + padding: '2px 4px 2px 4px', + fontSize: '9px' + }) + .html(Ox.formatDuration(self.options.position)) + .appendTo(self.$bottomBar); + + self.$panel = Ox.SplitPanel({ + elements: [ + { + element: self.$topBar, + size: 16 + }, + { + element: self.$content + }, + { + element: self.$bottomBar, + size: 16 + } + ], + orientation: 'vertical' + }); + + that.setElement(self.$panel); + + self.$lines = []; + self.$timelines = []; + self.$interfaces = []; + + self.$timeline = renderTimeline(); + //setSubtitles(); + Ox.loop(self.lines, function(i) { + addLine(i); + }); + + self.$frameBox = $('
') + .css({ + position: 'absolute', + right: 0, + top: self.margin / 2 - 1 + 'px', + width: self.videoWidth + 'px', + height: self.tileHeight + 'px', + borderTop: '1px solid rgba(255, 255, 255, 0.5)', + borderBottom: '1px solid rgba(255, 255, 255, 0.5)', + background: 'rgb(0, 0, 0)' + }) + .appendTo(self.$timelines[self.videoLines[1]][0]); + self.$frame = $('') + .attr({ + src: self.options.getFrameURL(self.options.position) + }) + .css({ + width: self.videoWidth + 'px', + height: self.tileHeight + 'px', + }) + .appendTo(self.$frameBox) + $('
') + .addClass('OxFrameInterface') + .css({ + position: 'absolute', + left: 0, + top: 0, + width: self.videoWidth + 'px', + height: self.tileHeight + 'px', + }) + .appendTo(self.$frameBox); + + self.$videoBox = $('
') + .css({ + position: 'absolute', + right: 0, + top: self.margin / 2 - 1 + 'px', + width: self.videoWidth + 'px', + height: self.tileHeight + 'px', + borderTop: '1px solid rgba(255, 255, 255, 0.5)', + borderBottom: '1px solid rgba(255, 255, 255, 0.5)', + background: 'rgb(0, 0, 0)', + zIndex: 5 + }) + .appendTo(self.$timelines[self.videoLines[0]][0]); + self.$video = Ox.VideoPlayer({ + duration: self.options.duration, + height: self.tileHeight, + paused: self.options.paused, + position: self.options.position, + scaleToFill: true, + video: self.options.videoURL, + width: self.videoWidth + }) + .bindEvent({ + ended: function() { + togglePaused(true); + }, + playing: function(data) { + self.options.position = data.position; + setPosition(true); + } + }) + .appendTo(self.$videoBox); + $('
') + .addClass('OxFrameInterface OxVideoInterface') + .css({ + position: 'absolute', + left: 0, + top: 0, + width: self.videoWidth + 'px', + height: self.tileHeight + 'px', + }) + .appendTo(self.$videoBox); + + self.$tooltip = Ox.Tooltip({ + animate: false + }) + .css({ + textAlign: 'center' + }); + + setTimeout(function() { + scrollToPosition(); + }); + + function addLine(i) { + self.$lines[i] = $('
') + .css({ + position: 'absolute', + left: self.margin + 'px', + top: self.margin / 2 + i * (self.tileHeight + self.margin) + 'px', + width: self.contentWidth + 'px', + height: self.tileHeight + self.margin + 'px', + overflowX: 'hidden' + }) + .appendTo(self.$content); + self.$timelines[i] = [ + self.$timeline.clone() + .css({ + width: self.frame + self.videoWidth + 'px', + marginLeft: -i * self.contentWidth + 'px', + }), + self.$timeline.clone() + .css({ + marginLeft: -i * self.contentWidth + self.videoWidth - 1 + 'px', + }) + ]; + self.$lines[i] + .append(self.$timelines[i][1]) + .append(self.$timelines[i][0]); + + } + + function getLines() { + return Math.ceil((self.frames - 1 + self.videoWidth) / self.contentWidth) + } + + function getPosition(e) { + return ( + e.offsetX ? e.offsetX + : e.clientX - $(e.target).offset().left + ) / self.fps; + } + + function getSubtitle(position) { + var subtitle = ''; + Ox.forEach(self.options.subtitles, function(v) { + if (v['in'] <= position && v.out > position) { + subtitle = v; + return false; + } + }); + return subtitle; + } + + function getVideoLine() { + self.videoLine = Math.floor(getVideoFrame() / self.contentWidth); + } + + function getVideoLines() { + var videoFrame = getVideoFrame(), + videoLeft = videoFrame % self.contentWidth, + lines = []; + lines[0] = Math.floor(videoFrame / self.contentWidth); + lines[1] = lines[0] + ( + videoLeft + self.videoWidth > self.contentWidth ? 1 : 0 + ) + if (videoLeft + Math.floor(self.videoWidth / 2) > self.contentWidth) { + lines.reverse(); + } + return lines; + } + + function getVideoFrame() { + return Math.floor(self.options.position * self.fps); + } + + function mousedown(e) { + var $target = $(e.target), + isTimeline = $target.is('.OxTimelineInterface'), + isVideo = $target.is('.OxFrameInterface'); + if (isTimeline) { + self.options.position = getPosition(e); + setPosition(); + if (!self.triggered) { + that.triggerEvent('position', { + position: self.options.position + }); + self.triggered = true; + setTimeout(function() { + self.triggered = false; + }, 250); + } + } else if (isVideo) { + togglePaused(); + } + } + + function mouseleave() { + self.$tooltip.hide(); + } + + function mousemove(e) { + var $target = $(e.target), + isTimeline = $target.is('.OxTimelineInterface'), + isVideo = $target.is('.OxFrameInterface'), + position, subtitle; + if (isTimeline || isVideo) { + position = isTimeline ? getPosition(e) : self.options.position; + subtitle = getSubtitle(position); + self.$tooltip.options({ + title: ( + subtitle + ? '' + + Ox.highlight(subtitle.text, self.options.find, 'OxHighlight').replace(/\n/g, '
') + + '

' + : '' + ) + Ox.formatDuration(position, 3) + }) + .show(e.clientX, e.clientY); + } else { + self.$tooltip.hide(); + } + } + + function renderTimeline() { + var $timeline = $('
') + .css({ + position: 'absolute', + width: self.frames + 'px', + height: self.tileHeight + self.margin + 'px', + overflow: 'hidden' + }); + Ox.loop(self.tiles, function(i) { + $('') + .attr({ + src: self.options.getImageURL(i) + }) + .css({ + position: 'absolute', + left: i * self.tileWidth + 'px', + top: self.margin / 2 + 'px' + }) + .appendTo($timeline); + }); + $('
') + .addClass('OxTimelineInterface') + .css({ + position: 'absolute', + left: 0, + top: self.margin / 2 + 'px', + width: self.frames + 'px', + height: self.tileHeight + 'px', + //background: 'rgba(255, 0, 0, 0.5)' + }) + .appendTo($timeline); + + return $timeline; + } + + function scrollToPosition() { + var scrollTop = self.$content.scrollTop(), + videoTop = [ + self.margin + Ox.min(self.videoLines) * (self.tileHeight + self.margin), + self.margin + Ox.max(self.videoLines) * (self.tileHeight + self.margin) + ], + offset = self.contentHeight - self.tileHeight - self.margin; + if (videoTop[0] < scrollTop + self.margin) { + self.$content.scrollTop(videoTop[0] - self.margin); + } else if (videoTop[1] > scrollTop + offset) { + self.$content.scrollTop(videoTop[1] - offset); + } + } + + function setPosition(fromVideo) { + var max, min, videoLines, isPlaying = !self.options.paused; + self.options.position = Ox.limit(self.options.position, 0, self.options.duration); + self.frame = Math.floor(self.options.position * self.fps); + videoLines = getVideoLines(); + min = Ox.min(Ox.flatten([self.videoLines, videoLines])); + max = Ox.max(Ox.flatten([self.videoLines, videoLines])); + Ox.loop(min, max + 1, function(i) { + self.$timelines[i][0].css({ + width: self.frame + self.videoWidth + 'px' + }); + }); + if (videoLines[1] != self.videoLines[1]) { + self.videoLines[1] = videoLines[1]; + self.$frameBox.detach().appendTo(self.$timelines[videoLines[1]][0]); + } + if (videoLines[0] != self.videoLines[0]) { + self.videoLines[0] = videoLines[0]; + isPlaying && self.$video.togglePaused(); + self.$videoBox.detach().appendTo(self.$timelines[videoLines[0]][0]); + isPlaying && self.$video.togglePaused(); + } + if (videoLines[0] != videoLines[1]) { + self.$frame.attr({ + src: self.options.getFrameURL( + self.paused + ? self.options.position + : Math.floor(self.options.position) + ) + }); + } + self.videoLines = videoLines; + if (!fromVideo) { + self.$video.options({position: self.options.position}); + self.$frame.attr({ + src: self.options.getFrameURL(self.options.position) + }); + scrollToPosition(); + } + self.$position.html(Ox.formatDuration(self.options.position)) + } + + function setSubtitles() { + self.$timeline.find('.OxSubtitle').remove(); + self.$subtitles = []; + self.options.subtitles.forEach(function(subtitle, i) { + var found = self.options.find + && subtitle.text.toLowerCase().indexOf(self.options.find.toLowerCase()) > -1; + self.$subtitles[i] = $('
') + .addClass('OxSubtitle' + (found ? ' OxHighlight' : '')) + .css({ + position: 'absolute', + left: (subtitle['in'] * self.fps) + 'px', + width: (((subtitle.out - subtitle['in']) * self.fps) - 2) + 'px' + }) + .html(Ox.highlight(subtitle.text, self.options.find, 'OxHighlight')) + .appendTo(self.$timeline); + }); + } + + function setWidth() { + self.contentWidth = self.options.width - 2 * self.margin - Ox.UI.SCROLLBAR_SIZE; + self.lines = getLines(); + Ox.loop(self.lines, function(i) { + if (self.$lines[i]) { + self.$lines[i].css({ + width: self.contentWidth + 'px' + }); + self.$timelines[i][0].css({ + marginLeft: -i * self.contentWidth + 'px' + }); + self.$timelines[i][1].css({ + marginLeft: -i * self.contentWidth + self.videoWidth + 'px' + }); + } else { + addLine(i); + } + }); + while (self.$lines.length > self.lines) { + self.$lines[self.$lines.length - 1].remove(); + self.$lines.pop(); + self.$timelines.pop(); + } + } + + function togglePaused(fromVideo) { + self.options.paused = !self.options.paused; + !fromVideo && self.$video.options({ + paused: self.options.paused + }); + self.$playButton.options({ + title: !self.options.paused ? 'pause' : 'play' + }); + } + + self.setOption = function(key, value) { + if (key == 'width') { + setWidth(); + } + }; + + return that; + +};