├── README.md ├── assets └── bigbuckbunny.webm ├── css └── style.css ├── index.html ├── js └── index.js └── vendor ├── ansiparse.js ├── ffmpeg-worker-webm.js ├── log.js ├── rangeslider.css └── rangeslider.js /README.md: -------------------------------------------------------------------------------- 1 | # browser-video-editor 2 | Edit videos in the browser with ffmpeg.js 3 | 4 | WIP: Experimental start with subclip editor 5 | -------------------------------------------------------------------------------- /assets/bigbuckbunny.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skomski/browser-video-editor/f88d1ce7d17b8711fd3096659e43f1b7477ecfa5/assets/bigbuckbunny.webm -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | .log { 2 | position: relative; 3 | margin-top: 35px 4 | } 5 | 6 | .log .log-tail { 7 | z-index: 80; 8 | position: absolute; 9 | display: block; 10 | top: 0; 11 | right: 0; 12 | margin: 10px 10px 0 0 13 | } 14 | 15 | .log .log-tail .tail-label { 16 | display: none; 17 | cursor: pointer 18 | } 19 | 20 | .log .log-tail:hover .tail-label { 21 | display: inline-block 22 | } 23 | 24 | .log .log-tail:hover .tail-status { 25 | display: none 26 | } 27 | 28 | .log .log-tail.scrolling { 29 | position: fixed; 30 | right: 32px 31 | } 32 | 33 | .log .log-tail.bottom { 34 | bottom: 45px; 35 | top: inherit 36 | } 37 | 38 | .log .log-tail .tail-status { 39 | position: relative; 40 | display: inline-block; 41 | height: 20px; 42 | width: 20px; 43 | vertical-align: middle; 44 | background-color: #696867; 45 | border-radius: 50% 46 | } 47 | 48 | .log .log-tail .tail-status:after { 49 | content: ""; 50 | display: block; 51 | height: 10px; 52 | width: 10px; 53 | background: #F2F2F2; 54 | border-radius: 50% 55 | } 56 | 57 | .log .log-tail.active .tail-status { 58 | background-color: #6b0 59 | } 60 | 61 | .log .to-top { 62 | z-index: 80; 63 | position: absolute; 64 | display: block; 65 | bottom: 2px; 66 | right: 2px; 67 | margin-right: 2px; 68 | padding-right: 16px; 69 | text-align: right; 70 | color: #999; 71 | background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iNSIgd2lkdGg9IjEwIj4KPHBhdGggZmlsbD0iI2MyYzJjMiIgZD0iTTEwLDUsNSwwLDAsNXoiLz4KPC9zdmc+Cgo=) right 6px no-repeat 72 | } 73 | 74 | .flash .log .close,.hooks-error .log .close,.log .flash .close,.log .hooks-error .close,.log .icon,.log .popup .close,.popup .log .close { 75 | width: 1.3em; 76 | height: 1.1em; 77 | margin-right: 6px; 78 | vertical-align: middle 79 | } 80 | 81 | .log .icon--down { 82 | width: .7em; 83 | height: .9em; 84 | margin-right: 4px 85 | } 86 | 87 | .log-header { 88 | height: 44px; 89 | margin: 0; 90 | padding: .7em .8em .6em; 91 | text-align: right; 92 | background-color: #444 93 | } 94 | 95 | .log-header a { 96 | margin-left: .4em 97 | } 98 | 99 | .log-body { 100 | position: relative 101 | } 102 | 103 | .log-body pre { 104 | clear: left; 105 | min-height: 42px; 106 | padding: 15px 0; 107 | color: #F1F1F1; 108 | font-family: monospace; 109 | font-size: 12px; 110 | line-height: 19px; 111 | white-space: pre-wrap; 112 | word-wrap: break-word; 113 | background-color: #2a2a2a; 114 | counter-reset: line-numbering; 115 | margin: 0 116 | } 117 | 118 | .log-body .cut { 119 | padding: 20px 15px 0 55px 120 | } 121 | 122 | .log-body p { 123 | position: relative; 124 | padding: 0 15px 0 55px; 125 | margin: 0; 126 | min-height: 16px 127 | } 128 | 129 | .log-body p:hover { 130 | background-color: #444!important 131 | } 132 | 133 | .log-body p.highlight { 134 | background-color: #666 135 | } 136 | 137 | .log-body p.highlight a { 138 | color: #fff 139 | } 140 | 141 | .log-body p a { 142 | display: inline-block; 143 | text-align: right; 144 | min-width: 40px; 145 | margin-left: -33px; 146 | cursor: pointer; 147 | text-decoration: none 148 | } 149 | 150 | .log-body p a::before { 151 | content: counter(line-numbering); 152 | counter-increment: line-numbering; 153 | padding-right: 1em 154 | } 155 | 156 | .log-body .fold { 157 | position: relative; 158 | height: 16px; 159 | overflow: hidden; 160 | cursor: pointer 161 | } 162 | 163 | .log-body .fold.open { 164 | height: auto 165 | } 166 | 167 | .log-body .fold p:first-of-type { 168 | padding-right: 190px; 169 | background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAiIHdpZHRoPSIxMCI+CjxwYXRoIGQ9Im0wLjUsMS41LDQuNSw3LDQuNS03eiIgc3Ryb2tlPSIjNTU1IiBzdHJva2Utd2lkdGg9IjAuNSIgZmlsbD0iIzY2NiIvPgo8L3N2Zz4KCg==) 8px 3px no-repeat #333 170 | } 171 | 172 | .log-body .fold p:first-of-type.highlight { 173 | background-color: #777 174 | } 175 | 176 | .log-body .fold:not(.open) p:first-of-type { 177 | visibility: visible; 178 | height: auto; 179 | min-height: 16px; 180 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAiIHdpZHRoPSIxMCI+CjxwYXRoIGQ9Ik0yLDksOSw1LDIsMXoiIHN0cm9rZT0iIzU1NSIgc3Ryb2tlLXdpZHRoPSIwLjUiIGZpbGw9IiM2NjYiLz4KPC9zdmc+Cgo=) 181 | } 182 | 183 | .log-body .fold .fold-name { 184 | position: absolute; 185 | z-index: 1; 186 | display: block; 187 | top: 2px; 188 | right: 85px; 189 | padding: 0 7px 2px; 190 | line-height: 10px; 191 | font-size: 10px; 192 | background-color: #666; 193 | border-radius: 6px; 194 | color: #bbb 195 | } 196 | 197 | .log-body .fold-end,.log-body .fold-start:not(.fold) { 198 | display: none 199 | } 200 | 201 | .log-body .duration { 202 | position: absolute; 203 | display: block; 204 | top: 2px; 205 | right: 12px; 206 | padding: 0 7px 2px; 207 | line-height: 10px; 208 | font-size: 10px; 209 | background-color: #666; 210 | border-radius: 6px; 211 | color: #bbb 212 | } 213 | 214 | .log-body .loading { 215 | padding: 25px 0 0 10px 216 | } 217 | 218 | .log-notice { 219 | background-color: #A6ADAD; 220 | color: #fff; 221 | min-height: 70px; 222 | line-height: 35px; 223 | text-align: center 224 | } 225 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ffmpeg.js browser example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 19 |
20 |
21 |

Input

22 |
23 |
24 |
25 |
26 |
27 | 28 |
29 | 30 |

Select a video file as source

31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 |
46 | 47 |
48 | 49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 |
59 | 60 | 61 |
62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |

Output

71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | Terminal 79 |
80 |
81 |
82 |             
83 |
84 |
85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var inputFile; 5 | var filesElement; 6 | var Logger; 7 | 8 | function LogOutput(text) { Logger.set(Logger.num++, text); } 9 | 10 | var lastCommand; 11 | 12 | function LogNewCommand(name, text) { 13 | LogEndCommand(0); 14 | lastCommand = `${name}-${Logger.num}`; 15 | LogOutput(`fold:start:${lastCommand}\x1B\[K\n`); 16 | LogOutput(text); 17 | } 18 | 19 | function LogEndCommand(exitCode) { 20 | if (lastCommand) { 21 | LogOutput(`exit code: ${exitCode}\nfold:end:${lastCommand}\x1B\[K\n`); 22 | } 23 | lastCommand = undefined; 24 | } 25 | 26 | function IsSupported() { 27 | return document.querySelector && window.URL && window.Worker; 28 | } 29 | 30 | function parseArguments(text) { 31 | text = text.replace(/\s+/g, ' '); 32 | var args = []; 33 | // Allow double quotes to not split args. 34 | text.split('"').forEach(function(t, i) { 35 | t = t.trim(); 36 | if ((i % 2) === 1) { 37 | args.push(t); 38 | } else { 39 | args = args.concat(t.split(" ")); 40 | } 41 | }); 42 | return args; 43 | } 44 | 45 | function RunMp4Conversion() { 46 | if (!inputFile) 47 | return alert('You need to select a source file!'); 48 | 49 | var startTime = document.getElementById("start").value; 50 | var duration = document.getElementById("duration").value; 51 | var scale = document.getElementById("scale").value; 52 | var fps = document.getElementById("fps").value; 53 | var filters = `fps=${fps},scale=${scale}:-1`; 54 | 55 | var inputName = inputFile.name; 56 | 57 | var reader = new FileReader(); 58 | 59 | reader.addEventListener("loadend", function() { 60 | var createMp4Command = 61 | `-hide_banner -ss ${startTime} -t ${duration} -i "${inputName}" 62 | -vf "${filters}" -pix_fmt yuv420p -strict -2 -y output.mp4`; 63 | 64 | RunCommand( 65 | { 66 | text : createMp4Command, 67 | data : [ {name : inputName, data : new Uint8Array(reader.result)} ], 68 | prettyName : 'convert-to-mp4' 69 | }, 70 | function(err, buffers) { 71 | if (err) 72 | throw err; 73 | 74 | buffers.forEach(function(file) { 75 | filesElement.appendChild( 76 | getDownloadLink(file.data, file.name, 'video/mp4')); 77 | }); 78 | LogOutput(`\nConverted ${inputName} to mp4!`); 79 | }); 80 | }); 81 | reader.readAsArrayBuffer(inputFile); 82 | } 83 | 84 | function RunGifConversion() { 85 | if (!inputFile) 86 | return alert('You need to select a source file!'); 87 | 88 | var startTime = document.getElementById("start").value; 89 | var duration = document.getElementById("duration").value; 90 | var scale = document.getElementById("scale").value; 91 | var fps = document.getElementById("fps").value; 92 | var palette = 'palette.jpg'; 93 | var filters = `fps=${fps},scale=${scale}:-1:flags=lanczos`; 94 | 95 | var inputName = inputFile.name; 96 | 97 | var reader = new FileReader(); 98 | 99 | reader.addEventListener("loadend", function() { 100 | var createPaletteCommand = 101 | `-hide_banner -ss ${startTime} -t ${duration} -i "${inputName}" 102 | -vf "${filters}, palettegen" -y ${palette}`; 103 | 104 | var createGifCommand = 105 | `-hide_banner -ss ${startTime} -t ${duration} -i "${inputName}" 106 | -i ${palette} -lavfi "${filters} [x]; [x][1:v] paletteuse" 107 | -y output.gif`; 108 | 109 | RunCommand( 110 | { 111 | text : createPaletteCommand, 112 | data : [ {name : inputName, data : new Uint8Array(reader.result)} ], 113 | prettyName : 'gif-palettegen' 114 | }, 115 | function(err, buffers) { 116 | if (err) 117 | throw err; 118 | if (buffers.length === 0) 119 | LogOutput('\nFailed to generate palette for ' + inputName); 120 | 121 | RunCommand( 122 | { 123 | text : createGifCommand, 124 | prettyName : 'convert-to-gif', 125 | data : [ 126 | {name : inputName, data : new Uint8Array(reader.result)}, 127 | {name : buffers[0].name, data : buffers[0].data} 128 | ] 129 | }, 130 | function(err, buffers) { 131 | if (err) 132 | throw err; 133 | buffers.forEach(function(file) { 134 | filesElement.appendChild( 135 | getDownloadLink(file.data, file.name)); 136 | }); 137 | LogOutput(`\nConverted ${inputName} to gif!`); 138 | }); 139 | }); 140 | }); 141 | reader.readAsArrayBuffer(inputFile); 142 | } 143 | 144 | function GetRandomInt(min, max) { 145 | return Math.floor(Math.random() * (max - min)) + min; 146 | } 147 | 148 | function getDownloadLink(fileData, fileName, fileType) { 149 | if (fileData instanceof Blob) { 150 | var blob = fileData; 151 | var src = window.URL.createObjectURL(fileData); 152 | } else { 153 | var blob = new Blob([ fileData ]); 154 | var src = window.URL.createObjectURL(blob); 155 | } 156 | 157 | var container = document.createElement('div'); 158 | container.className = 'row'; 159 | 160 | var col1 = document.createElement('div'); 161 | col1.className = 'col-md-8'; 162 | 163 | var col2 = document.createElement('div'); 164 | col2.className = 'col-md-4'; 165 | 166 | var header = document.createElement('h3'); 167 | header.textContent = fileName; 168 | 169 | var downloadLink = document.createElement('a'); 170 | downloadLink.download = fileName; 171 | downloadLink.href = src; 172 | downloadLink.className = 'btn btn-default btn-block'; 173 | downloadLink.textContent = 'Download'; 174 | 175 | var imgurLink = document.createElement('a'); 176 | imgurLink.className = 'btn btn-default btn-block'; 177 | imgurLink.textContent = 'Export to imgur.com'; 178 | imgurLink.onclick = 179 | function(e) { 180 | var formData = new FormData(); 181 | formData.append('image', blob); 182 | $.ajax({ 183 | url : 'https://api.imgur.com/3/image', 184 | type : 'POST', 185 | data : formData, 186 | cache : false, 187 | contentType : false, 188 | processData : false, 189 | headers : {Authorization : 'Client-ID 9b029b06fdc00ba'}, 190 | success : function(data) { 191 | imgurLink.href = "https://i.imgur.com/" + data.data.id; 192 | imgurLink.className += ' btn-success'; 193 | imgurLink.target = '_blank'; 194 | imgurLink.textContent = 'Uploaded to ' + imgurLink.href; 195 | }, 196 | error : function(xhr, textStatus, error) { 197 | imgurLink.className += ' btn-warning'; 198 | imgurLink.textContent = `${error}: ${xhr.responseJSON.data.error}`; 199 | }, 200 | xhr : function() { 201 | var xhr = new window.XMLHttpRequest(); 202 | xhr.upload.addEventListener("progress", function(evt) { 203 | if (evt.lengthComputable) { 204 | var percentComplete = evt.loaded / evt.total; 205 | imgurLink.textContent = 206 | 'Uploading: ' + percentComplete * 100 + '%'; 207 | } else { 208 | imgurLink.textContent = 'Uploading...'; 209 | } 210 | }, false); 211 | return xhr; 212 | } 213 | }); 214 | } 215 | 216 | var streamableLink = document.createElement('a'); 217 | streamableLink.className = 'btn btn-default btn-block'; 218 | streamableLink.textContent = 'Export to streamable.com'; 219 | streamableLink.onclick = 220 | function(e) { 221 | var formData = new FormData(); 222 | formData.append('File', blob); 223 | $.ajax({ 224 | url : 'https://api.streamable.com/upload', 225 | type : 'POST', 226 | data : formData, 227 | cache : false, 228 | contentType : false, 229 | processData : false, 230 | success : function(data) { 231 | streamableLink.href = "https://streamable.com/" + data.shortcode; 232 | streamableLink.className += ' btn-success'; 233 | streamableLink.target = '_blank'; 234 | streamableLink.textContent = 'Uploaded to ' + streamableLink.href; 235 | }, 236 | error : function(xhr, textStatus, error) { 237 | streamableLink.className += ' btn-warning'; 238 | streamableLink.textContent = 239 | `${error}: ${xhr.responseJSON.data.error}`; 240 | }, 241 | xhr : function() { 242 | var xhr = new window.XMLHttpRequest(); 243 | xhr.upload.addEventListener("progress", function(evt) { 244 | if (evt.lengthComputable) { 245 | var percentComplete = evt.loaded / evt.total; 246 | streamableLink.textContent = 247 | 'Uploading: ' + percentComplete * 100 + '%'; 248 | } else { 249 | streamableLink.textContent = 'Uploading...'; 250 | } 251 | }, false); 252 | return xhr; 253 | } 254 | }); 255 | }; 256 | 257 | col2.appendChild(header); 258 | col2.appendChild(downloadLink); 259 | col2.appendChild(streamableLink); 260 | col2.appendChild(imgurLink); 261 | 262 | if (fileName.match(/\.jpeg|\.gif|\.jpg|\.png/)) { 263 | var img = document.createElement('img'); 264 | img.src = src; 265 | img.className = 'img-thumbnail'; 266 | col1.appendChild(img); 267 | } else { 268 | var video = document.createElement('video'); 269 | video.controls = true; 270 | video.width = 640; 271 | video.height = 480; 272 | video.id = 'preview-video-' + (Math.random() * 1000).toFixed(0); 273 | video.className = 'video-js vjs-default-skin'; 274 | 275 | var source = document.createElement('source'); 276 | source.src = src; 277 | source.type = fileType || fileData.type; 278 | video.appendChild(source); 279 | 280 | col1.appendChild(video); 281 | } 282 | 283 | container.appendChild(col1); 284 | container.appendChild(col2); 285 | return container; 286 | } 287 | 288 | function RunCommand(options, cb) { 289 | var worker = new Worker("../vendor/ffmpeg-worker-webm.js"); 290 | var lastError; 291 | 292 | worker.onmessage = function(event) { 293 | var message = event.data; 294 | switch (message.type) { 295 | case 'ready': 296 | break; 297 | case 'stdout': 298 | LogOutput(message.data + "\n"); 299 | break; 300 | case 'stderr': 301 | LogOutput(message.data + "\n"); 302 | break; 303 | case 'start': 304 | break; 305 | case 'done': 306 | var buffers = message.data.MEMFS; 307 | if (cb) 308 | cb(lastError, buffers); 309 | worker.terminate(); 310 | break; 311 | case 'exit': 312 | LogEndCommand(message.data); 313 | if (message.data > 0) 314 | lastError = new Error(`exit code: ${message.data}`); 315 | break; 316 | case 'run': 317 | break; 318 | default: 319 | throw new Error('Unhandled switch case', message.type); 320 | } 321 | }; 322 | 323 | var args = parseArguments(options.text); 324 | LogNewCommand(options.prettyName || args[0], 325 | '$ ffmpeg ' + args.join(' ') + '\n'); 326 | worker.postMessage({type : "run", arguments : args, MEMFS : options.data}); 327 | } 328 | 329 | function handleFileSelect(evt) { 330 | document.getElementById('inputpreview').innerHTML = ''; 331 | 332 | var files = evt.target.files; // FileList object 333 | if (files.length === 0) 334 | return; 335 | inputFile = files[0]; 336 | 337 | var newElement = getDownloadLink(inputFile, inputFile.name); 338 | document.getElementById('inputpreview').appendChild(newElement); 339 | if (inputFile.name.match(/\.jpeg|\.gif|\.jpg|\.png/)) return; 340 | var videoPlayer = videojs(newElement.firstChild.firstChild.id); 341 | videoPlayer.rangeslider(); 342 | videoPlayer.ready(function() { 343 | videoPlayer.volume(0); 344 | videoPlayer.play(); 345 | videoPlayer.on('sliderchange', function() { 346 | var values = videoPlayer.getValueSlider(); 347 | document.getElementById('start').value = values.start; 348 | document.getElementById('duration').value = values.end - values.start; 349 | }); 350 | videoPlayer.on('loadedRangeSlider', function() { 351 | videoPlayer.pause(); 352 | videoPlayer.setValueSlider(0, 5); 353 | }); 354 | }); 355 | } 356 | 357 | document.addEventListener("DOMContentLoaded", function() { 358 | if (!IsSupported()) { 359 | alert(`This website is not supported by your browser! 360 | Update to new Chrome or Firefox.`); 361 | return; 362 | } 363 | 364 | document.getElementById('files') 365 | .addEventListener('change', handleFileSelect, false); 366 | 367 | filesElement = document.querySelector("#outputfiles"); 368 | 369 | document.getElementById('uploadForm') 370 | .onsubmit = function(e) { return false; }; 371 | 372 | $('#convertgif') 373 | .on('click', function(e) { 374 | RunGifConversion(); 375 | return true; 376 | }); 377 | 378 | $('#convertmp4') 379 | .on('click', function(e) { 380 | RunMp4Conversion(); 381 | return true; 382 | }); 383 | 384 | Logger = Log.create(); 385 | Logger.num = 0; 386 | LogOutput('Loading JavaScript files (it may take a minute)\n'); 387 | 388 | window.onerror = function(msg, url, line, col, error) { 389 | LogOutput(`\n${msg} line: ${line} col: ${col} url: ${url}\n`); 390 | }; 391 | 392 | $('#log') 393 | .on('click', '.fold', 394 | function() { return $(this).toggleClass('open'); }); 395 | 396 | RunCommand({text : '-version -hide_banner'}, function(err) { 397 | if (err) 398 | throw err; 399 | RunCommand({text : '-formats -hide_banner'}, function(err) { 400 | if (err) 401 | throw err; 402 | LogOutput('Sample commands executing fine!'); 403 | }); 404 | }); 405 | }); 406 | })(); 407 | -------------------------------------------------------------------------------- /vendor/ansiparse.js: -------------------------------------------------------------------------------- 1 | ansiparse = function (str) { 2 | // 3 | // I'm terrible at writing parsers. 4 | // 5 | var matchingControl = null, 6 | matchingData = null, 7 | matchingText = '', 8 | ansiState = [], 9 | result = [], 10 | state = {}, 11 | eraseChar; 12 | 13 | // 14 | // General workflow for this thing is: 15 | // \033\[33mText 16 | // | | | 17 | // | | matchingText 18 | // | matchingData 19 | // matchingControl 20 | // 21 | // In further steps we hope it's all going to be fine. It usually is. 22 | // 23 | 24 | // 25 | // Erases a char from the output 26 | // 27 | eraseChar = function () { 28 | var index, text; 29 | if (matchingText.length) { 30 | matchingText = matchingText.substr(0, matchingText.length - 1); 31 | } 32 | else if (result.length) { 33 | index = result.length - 1; 34 | text = result[index].text; 35 | if (text.length === 1) { 36 | // 37 | // A result bit was fully deleted, pop it out to simplify the final output 38 | // 39 | result.pop(); 40 | } 41 | else { 42 | result[index].text = text.substr(0, text.length - 1); 43 | } 44 | } 45 | }; 46 | 47 | for (var i = 0; i < str.length; i++) { 48 | if (matchingControl != null) { 49 | if (matchingControl == '\033' && str[i] == '\[') { 50 | // 51 | // We've matched full control code. Lets start matching formating data. 52 | // 53 | 54 | // 55 | // "emit" matched text with correct state 56 | // 57 | if (matchingText) { 58 | state.text = matchingText; 59 | result.push(state); 60 | state = {}; 61 | matchingText = ""; 62 | } 63 | 64 | matchingControl = null; 65 | matchingData = ''; 66 | } 67 | else { 68 | // 69 | // We failed to match anything - most likely a bad control code. We 70 | // go back to matching regular strings. 71 | // 72 | matchingText += matchingControl + str[i]; 73 | matchingControl = null; 74 | } 75 | continue; 76 | } 77 | else if (matchingData != null) { 78 | if (str[i] == ';') { 79 | // 80 | // `;` separates many formatting codes, for example: `\033[33;43m` 81 | // means that both `33` and `43` should be applied. 82 | // 83 | // TODO: this can be simplified by modifying state here. 84 | // 85 | ansiState.push(matchingData); 86 | matchingData = ''; 87 | } 88 | else if (str[i] == 'm') { 89 | // 90 | // `m` finished whole formatting code. We can proceed to matching 91 | // formatted text. 92 | // 93 | ansiState.push(matchingData); 94 | matchingData = null; 95 | matchingText = ''; 96 | 97 | // 98 | // Convert matched formatting data into user-friendly state object. 99 | // 100 | // TODO: DRY. 101 | // 102 | ansiState.forEach(function (ansiCode) { 103 | if (ansiparse.foregroundColors[ansiCode]) { 104 | state.foreground = ansiparse.foregroundColors[ansiCode]; 105 | } 106 | else if (ansiparse.backgroundColors[ansiCode]) { 107 | state.background = ansiparse.backgroundColors[ansiCode]; 108 | } 109 | else if (ansiCode == 39) { 110 | delete state.foreground; 111 | } 112 | else if (ansiCode == 49) { 113 | delete state.background; 114 | } 115 | else if (ansiparse.styles[ansiCode]) { 116 | state[ansiparse.styles[ansiCode]] = true; 117 | } 118 | else if (ansiCode == 22) { 119 | state.bold = false; 120 | } 121 | else if (ansiCode == 23) { 122 | state.italic = false; 123 | } 124 | else if (ansiCode == 24) { 125 | state.underline = false; 126 | } 127 | }); 128 | ansiState = []; 129 | } 130 | else { 131 | matchingData += str[i]; 132 | } 133 | continue; 134 | } 135 | 136 | if (str[i] == '\033') { 137 | matchingControl = str[i]; 138 | } 139 | else if (str[i] == '\u0008') { 140 | eraseChar(); 141 | } 142 | else { 143 | matchingText += str[i]; 144 | } 145 | } 146 | 147 | if (matchingText) { 148 | state.text = matchingText + (matchingControl ? matchingControl : ''); 149 | result.push(state); 150 | } 151 | return result; 152 | } 153 | 154 | ansiparse.foregroundColors = { 155 | '30': 'black', 156 | '31': 'red', 157 | '32': 'green', 158 | '33': 'yellow', 159 | '34': 'blue', 160 | '35': 'magenta', 161 | '36': 'cyan', 162 | '37': 'white', 163 | '90': 'grey' 164 | }; 165 | 166 | ansiparse.backgroundColors = { 167 | '40': 'black', 168 | '41': 'red', 169 | '42': 'green', 170 | '43': 'yellow', 171 | '44': 'blue', 172 | '45': 'magenta', 173 | '46': 'cyan', 174 | '47': 'white' 175 | }; 176 | 177 | ansiparse.styles = { 178 | '1': 'bold', 179 | '3': 'italic', 180 | '4': 'underline' 181 | }; 182 | 183 | if (typeof module == "object" && typeof window == "undefined") { 184 | module.exports = ansiparse; 185 | } 186 | 187 | -------------------------------------------------------------------------------- /vendor/log.js: -------------------------------------------------------------------------------- 1 | var Log = function() { 2 | this.autoCloseFold = true; 3 | this.listeners = []; 4 | this.renderer = new Log.Renderer; 5 | this.children = new Log.Nodes(this); 6 | this.parts = {}; 7 | this.folds = new Log.Folds(this); 8 | this.times = new Log.Times(this); 9 | return this; 10 | }; 11 | 12 | Log.extend = function(one, other) { 13 | var name; 14 | for (name in other) { 15 | one[name] = other[name]; 16 | } 17 | return one; 18 | }; 19 | 20 | Log.extend(Log, { 21 | DEBUG: false, 22 | SLICE: 500, 23 | TIMEOUT: 25, 24 | FOLD: /fold:(start|end):([\w_\-\.]+)/, 25 | TIME: /time:(start|end):([\w_\-\.]+):?([\w_\-\.\=\,]*)/, 26 | create: function(options) { 27 | var listener, log, _i, _len, _ref; 28 | options || (options = {}); 29 | log = new Log(); 30 | if (options.limit) { 31 | log.listeners.push(log.limit = new Log.Limit(options.limit)); 32 | } 33 | _ref = options.listeners || []; 34 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 35 | listener = _ref[_i]; 36 | log.listeners.push(listener); 37 | } 38 | return log; 39 | } 40 | }); 41 | 42 | var newLineAtTheEndRegexp, newLineRegexp, rRegexp, removeCarriageReturns; 43 | 44 | Log.Node = function(id, num) { 45 | this.id = id; 46 | this.num = num; 47 | this.key = Log.Node.key(this.id); 48 | this.children = new Log.Nodes(this); 49 | return this; 50 | }; 51 | 52 | Log.extend(Log.Node, { 53 | key: function(id) { 54 | if (id) { 55 | return id.split('-').map(function(i) { 56 | return '000000'.concat(i).slice(-6); 57 | }).join(''); 58 | } 59 | } 60 | }); 61 | 62 | Log.extend(Log.Node.prototype, { 63 | addChild: function(node) { 64 | return this.children.add(node); 65 | }, 66 | remove: function() { 67 | this.log.remove(this.element); 68 | return this.parent.children.remove(this); 69 | } 70 | }); 71 | 72 | Object.defineProperty(Log.Node.prototype, 'log', { 73 | get: function() { 74 | var _ref; 75 | return this._log || (this._log = ((_ref = this.parent) != null ? _ref.log : void 0) || this.parent); 76 | } 77 | }); 78 | 79 | Object.defineProperty(Log.Node.prototype, 'firstChild', { 80 | get: function() { 81 | return this.children.first; 82 | } 83 | }); 84 | 85 | Object.defineProperty(Log.Node.prototype, 'lastChild', { 86 | get: function() { 87 | return this.children.last; 88 | } 89 | }); 90 | 91 | Log.Nodes = function(parent) { 92 | if (parent) { 93 | this.parent = parent; 94 | } 95 | this.items = []; 96 | this.index = {}; 97 | return this; 98 | }; 99 | 100 | Log.extend(Log.Nodes.prototype, { 101 | add: function(item) { 102 | var ix, next, prev, _ref, _ref1; 103 | ix = this.position(item) || 0; 104 | this.items.splice(ix, 0, item); 105 | if (this.parent) { 106 | item.parent = this.parent; 107 | } 108 | prev = function(item) { 109 | while (item && !item.children.last) { 110 | item = item.prev; 111 | } 112 | return item != null ? item.children.last : void 0; 113 | }; 114 | next = function(item) { 115 | while (item && !item.children.first) { 116 | item = item.next; 117 | } 118 | return item != null ? item.children.first : void 0; 119 | }; 120 | if (item.prev = this.items[ix - 1] || prev((_ref = this.parent) != null ? _ref.prev : void 0)) { 121 | item.prev.next = item; 122 | } 123 | if (item.next = this.items[ix + 1] || next((_ref1 = this.parent) != null ? _ref1.next : void 0)) { 124 | item.next.prev = item; 125 | } 126 | return item; 127 | }, 128 | remove: function(item) { 129 | this.items.splice(this.items.indexOf(item), 1); 130 | if (item.next) { 131 | item.next.prev = item.prev; 132 | } 133 | if (item.prev) { 134 | item.prev.next = item.next; 135 | } 136 | if (this.items.length === 0) { 137 | return this.parent.remove(); 138 | } 139 | }, 140 | position: function(item) { 141 | var ix, _i, _ref; 142 | for (ix = _i = _ref = this.items.length - 1; _i >= 0; ix = _i += -1) { 143 | if (this.items[ix].key < item.key) { 144 | return ix + 1; 145 | } 146 | } 147 | }, 148 | indexOf: function() { 149 | return this.items.indexOf.apply(this.items, arguments); 150 | }, 151 | slice: function() { 152 | return this.items.slice.apply(this.items, arguments); 153 | }, 154 | each: function(func) { 155 | return this.items.slice().forEach(func); 156 | }, 157 | map: function(func) { 158 | return this.items.map(func); 159 | } 160 | }); 161 | 162 | Object.defineProperty(Log.Nodes.prototype, 'first', { 163 | get: function() { 164 | return this.items[0]; 165 | } 166 | }); 167 | 168 | Object.defineProperty(Log.Nodes.prototype, 'last', { 169 | get: function() { 170 | return this.items[this.length - 1]; 171 | } 172 | }); 173 | 174 | Object.defineProperty(Log.Nodes.prototype, 'length', { 175 | get: function() { 176 | return this.items.length; 177 | } 178 | }); 179 | 180 | Log.Part = function(id, num, string) { 181 | Log.Node.apply(this, arguments); 182 | this.string = string || ''; 183 | this.string = this.string.replace(/\033\[1000D/gm, '\r'); 184 | this.string = this.string.replace(/\r+\n/gm, '\n'); 185 | this.strings = this.string.split(/^/gm) || []; 186 | this.slices = ((function() { 187 | var _results; 188 | _results = []; 189 | while (this.strings.length > 0) { 190 | _results.push(this.strings.splice(0, Log.SLICE)); 191 | } 192 | return _results; 193 | }).call(this)); 194 | return this; 195 | }; 196 | 197 | Log.extend(Log.Part, { 198 | create: function(log, num, string) { 199 | var part; 200 | part = new Log.Part(num.toString(), num, string); 201 | log.addChild(part); 202 | return part.process(0, -1); 203 | } 204 | }); 205 | 206 | Log.Part.prototype = Log.extend(new Log.Node, { 207 | remove: function() {}, 208 | process: function(slice, num) { 209 | var node, span, spans, string, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3, _ref4, 210 | _this = this; 211 | _ref = this.slices[slice] || []; 212 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 213 | string = _ref[_i]; 214 | if ((_ref1 = this.log.limit) != null ? _ref1.limited : void 0) { 215 | return; 216 | } 217 | spans = []; 218 | _ref2 = Log.Deansi.apply(string); 219 | for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { 220 | node = _ref2[_j]; 221 | span = Log.Span.create(this, "" + this.id + "-" + (num += 1), num, node.text, node["class"]); 222 | span.render(); 223 | spans.push(span); 224 | } 225 | if ((_ref3 = spans[0]) != null ? (_ref4 = _ref3.line) != null ? _ref4.cr : void 0 : void 0) { 226 | spans[0].line.clear(); 227 | } 228 | } 229 | if (!(slice >= this.slices.length - 1)) { 230 | return setTimeout((function() { 231 | return _this.process(slice + 1, num); 232 | }), Log.TIMEOUT); 233 | } 234 | } 235 | }); 236 | 237 | newLineAtTheEndRegexp = new RegExp("\n$"); 238 | 239 | newLineRegexp = new RegExp("\n"); 240 | 241 | rRegexp = new RegExp("\r"); 242 | 243 | removeCarriageReturns = function(string) { 244 | var index; 245 | index = string.lastIndexOf("\r"); 246 | if (index === -1) { 247 | return string; 248 | } 249 | return string.substr(index + 1); 250 | }; 251 | 252 | Log.Span = function(id, num, text, classes) { 253 | var fold, time, _ref; 254 | Log.Node.apply(this, arguments); 255 | if (fold = text.match(Log.FOLD)) { 256 | this.fold = true; 257 | this.event = fold[1]; 258 | this.text = this.name = fold[2]; 259 | } else if (time = text.match(Log.TIME)) { 260 | this.time = true; 261 | this.event = time[1]; 262 | this.name = time[2]; 263 | this.stats = time[3]; 264 | } else { 265 | this.text = text; 266 | this.text = removeCarriageReturns(this.text); 267 | this.text = this.text.replace(newLineAtTheEndRegexp, ''); 268 | this.nl = !!((_ref = text[text.length - 1]) != null ? _ref.match(newLineRegexp) : void 0); 269 | this.cr = !!text.match(rRegexp); 270 | this["class"] = this.cr && ['clears'] || classes; 271 | } 272 | return this; 273 | }; 274 | 275 | Log.extend(Log.Span, { 276 | create: function(parent, id, num, text, classes) { 277 | var span; 278 | span = new Log.Span(id, num, text, classes); 279 | parent.addChild(span); 280 | return span; 281 | }, 282 | render: function(parent, id, num, text, classes) { 283 | var span; 284 | span = this.create(parent, id, num, text, classes); 285 | return span.render(); 286 | } 287 | }); 288 | 289 | Log.Span.prototype = Log.extend(new Log.Node, { 290 | render: function() { 291 | var tail; 292 | if (this.time && this.event === 'end' && this.prev) { 293 | if (Log.DEBUG) { 294 | console.log("S.0 insert " + this.id + " after prev " + this.prev.id); 295 | } 296 | this.nl = this.prev.nl; 297 | this.log.insert(this.data, { 298 | after: this.prev.element 299 | }); 300 | this.line = this.prev.line; 301 | } else if (!this.fold && this.prev && !this.prev.fold && !this.prev.nl) { 302 | if (Log.DEBUG) { 303 | console.log("S.1 insert " + this.id + " after prev " + this.prev.id); 304 | } 305 | this.log.insert(this.data, { 306 | after: this.prev.element 307 | }); 308 | this.line = this.prev.line; 309 | } else if (!this.fold && this.next && !this.next.fold && !this.next.time) { 310 | if (Log.DEBUG) { 311 | console.log("S.2 insert " + this.id + " before next " + this.next.id); 312 | } 313 | this.log.insert(this.data, { 314 | before: this.next.element 315 | }); 316 | this.line = this.next.line; 317 | } else { 318 | this.line = Log.Line.create(this.log, [this]); 319 | this.line.render(); 320 | } 321 | if (this.nl && (tail = this.tail).length > 0) { 322 | this.split(tail); 323 | } 324 | if (this.time) { 325 | return this.log.times.add(this); 326 | } 327 | }, 328 | remove: function() { 329 | Log.Node.prototype.remove.apply(this); 330 | if (this.line) { 331 | return this.line.remove(this); 332 | } 333 | }, 334 | split: function(spans) { 335 | var line, span, _i, _len; 336 | if (Log.DEBUG) { 337 | console.log("S.4 split [" + (spans.map(function(span) { 338 | return span.id; 339 | }).join(', ')) + "]"); 340 | } 341 | for (_i = 0, _len = spans.length; _i < _len; _i++) { 342 | span = spans[_i]; 343 | this.log.remove(span.element); 344 | } 345 | line = Log.Line.create(this.log, spans); 346 | line.render(); 347 | if (line.cr) { 348 | return line.clear(); 349 | } 350 | }, 351 | clear: function() { 352 | if (this.prev && this.isSibling(this.prev) && this.isSequence(this.prev)) { 353 | this.prev.clear(); 354 | return this.prev.remove(); 355 | } 356 | }, 357 | isSequence: function(other) { 358 | return this.parent.num - other.parent.num === this.log.children.indexOf(this.parent) - this.log.children.indexOf(other.parent); 359 | }, 360 | isSibling: function(other) { 361 | var _ref, _ref1; 362 | return ((_ref = this.element) != null ? _ref.parentNode : void 0) === ((_ref1 = other.element) != null ? _ref1.parentNode : void 0); 363 | }, 364 | siblings: function(type) { 365 | var siblings, span; 366 | siblings = []; 367 | while ((span = (span || this)[type]) && this.isSibling(span)) { 368 | siblings.push(span); 369 | } 370 | return siblings; 371 | } 372 | }); 373 | 374 | Object.defineProperty(Log.Span.prototype, 'data', { 375 | get: function() { 376 | return { 377 | id: this.id, 378 | type: 'span', 379 | text: this.text, 380 | "class": this["class"], 381 | time: this.time 382 | }; 383 | } 384 | }); 385 | 386 | Object.defineProperty(Log.Span.prototype, 'line', { 387 | get: function() { 388 | return this._line; 389 | }, 390 | set: function(line) { 391 | if (this.line) { 392 | this.line.remove(this); 393 | } 394 | this._line = line; 395 | if (this.line) { 396 | return this.line.add(this); 397 | } 398 | } 399 | }); 400 | 401 | Object.defineProperty(Log.Span.prototype, 'element', { 402 | get: function() { 403 | return document.getElementById(this.id); 404 | } 405 | }); 406 | 407 | Object.defineProperty(Log.Span.prototype, 'head', { 408 | get: function() { 409 | return this.siblings('prev').reverse(); 410 | } 411 | }); 412 | 413 | Object.defineProperty(Log.Span.prototype, 'tail', { 414 | get: function() { 415 | return this.siblings('next'); 416 | } 417 | }); 418 | 419 | Log.Line = function(log) { 420 | this.log = log; 421 | this.spans = []; 422 | return this; 423 | }; 424 | 425 | Log.extend(Log.Line, { 426 | create: function(log, spans) { 427 | var line, span, _i, _len; 428 | if ((span = spans[0]) && span.fold) { 429 | line = new Log.Fold(log, span.event, span.name); 430 | } else { 431 | line = new Log.Line(log); 432 | } 433 | for (_i = 0, _len = spans.length; _i < _len; _i++) { 434 | span = spans[_i]; 435 | span.line = line; 436 | } 437 | return line; 438 | } 439 | }); 440 | 441 | Log.extend(Log.Line.prototype, { 442 | add: function(span) { 443 | var ix; 444 | if (span.cr) { 445 | this.cr = true; 446 | } 447 | if (this.spans.indexOf(span) > -1) { 448 | 449 | } else if ((ix = this.spans.indexOf(span.prev)) > -1) { 450 | return this.spans.splice(ix + 1, 0, span); 451 | } else if ((ix = this.spans.indexOf(span.next)) > -1) { 452 | return this.spans.splice(ix, 0, span); 453 | } else { 454 | return this.spans.push(span); 455 | } 456 | }, 457 | remove: function(span) { 458 | var ix; 459 | if ((ix = this.spans.indexOf(span)) > -1) { 460 | return this.spans.splice(ix, 1); 461 | } 462 | }, 463 | render: function() { 464 | var fold; 465 | if ((fold = this.prev) && fold.event === 'start' && fold.active) { 466 | if (this.next && !this.next.fold) { 467 | if (Log.DEBUG) { 468 | console.log("L.0 insert " + this.id + " before next " + this.next.id); 469 | } 470 | return this.element = this.log.insert(this.data, { 471 | before: this.next.element 472 | }); 473 | } else { 474 | if (Log.DEBUG) { 475 | console.log("L.0 insert " + this.id + " into fold " + fold.id); 476 | } 477 | fold = this.log.folds.folds[fold.name].fold; 478 | return this.element = this.log.insert(this.data, { 479 | into: fold 480 | }); 481 | } 482 | } else if (this.prev) { 483 | if (Log.DEBUG) { 484 | console.log("L.1 insert " + this.spans[0].id + " after prev " + this.prev.id); 485 | } 486 | return this.element = this.log.insert(this.data, { 487 | after: this.prev.element 488 | }); 489 | } else if (this.next) { 490 | if (Log.DEBUG) { 491 | console.log("L.2 insert " + this.spans[0].id + " before next " + this.next.id); 492 | } 493 | return this.element = this.log.insert(this.data, { 494 | before: this.next.element 495 | }); 496 | } else { 497 | if (Log.DEBUG) { 498 | console.log("L.3 insert " + this.spans[0].id + " into #log"); 499 | } 500 | return this.element = this.log.insert(this.data); 501 | } 502 | }, 503 | clear: function() { 504 | var cr, _i, _len, _ref, _results; 505 | _ref = this.crs; 506 | _results = []; 507 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 508 | cr = _ref[_i]; 509 | _results.push(cr.clear()); 510 | } 511 | return _results; 512 | } 513 | }); 514 | 515 | Object.defineProperty(Log.Line.prototype, 'id', { 516 | get: function() { 517 | var _ref; 518 | return (_ref = this.spans[0]) != null ? _ref.id : void 0; 519 | } 520 | }); 521 | 522 | Object.defineProperty(Log.Line.prototype, 'data', { 523 | get: function() { 524 | return { 525 | type: 'paragraph', 526 | nodes: this.nodes 527 | }; 528 | } 529 | }); 530 | 531 | Object.defineProperty(Log.Line.prototype, 'nodes', { 532 | get: function() { 533 | return this.spans.map(function(span) { 534 | return span.data; 535 | }); 536 | } 537 | }); 538 | 539 | Object.defineProperty(Log.Line.prototype, 'prev', { 540 | get: function() { 541 | var _ref; 542 | return (_ref = this.spans[0].prev) != null ? _ref.line : void 0; 543 | } 544 | }); 545 | 546 | Object.defineProperty(Log.Line.prototype, 'next', { 547 | get: function() { 548 | var _ref; 549 | return (_ref = this.spans[this.spans.length - 1].next) != null ? _ref.line : void 0; 550 | } 551 | }); 552 | 553 | Object.defineProperty(Log.Line.prototype, 'crs', { 554 | get: function() { 555 | return this.spans.filter(function(span) { 556 | return span.cr; 557 | }); 558 | } 559 | }); 560 | 561 | Log.Fold = function(log, event, name) { 562 | Log.Line.apply(this, arguments); 563 | this.fold = true; 564 | this.event = event; 565 | this.name = name; 566 | return this; 567 | }; 568 | 569 | Log.Fold.prototype = Log.extend(new Log.Line, { 570 | render: function() { 571 | var element, _ref; 572 | if (this.prev && this.prev.element) { 573 | if (Log.DEBUG) { 574 | console.log("F.1 insert " + this.id + " after prev " + this.prev.id); 575 | } 576 | element = this.prev.element; 577 | this.element = this.log.insert(this.data, { 578 | after: element 579 | }); 580 | } else if (this.next) { 581 | if (Log.DEBUG) { 582 | console.log("F.2 insert " + this.id + " before next " + this.next.id); 583 | } 584 | element = this.next.element || this.next.element.parentNode; 585 | this.element = this.log.insert(this.data, { 586 | before: element 587 | }); 588 | } else { 589 | if (Log.DEBUG) { 590 | console.log("F.3 insert " + this.id); 591 | } 592 | this.element = this.log.insert(this.data); 593 | } 594 | if (this.span.next && ((_ref = this.span.prev) != null ? _ref.isSibling(this.span.next) : void 0)) { 595 | this.span.prev.split([this.span.next].concat(this.span.next.tail)); 596 | } 597 | return this.active = this.log.folds.add(this.data); 598 | } 599 | }); 600 | 601 | Object.defineProperty(Log.Fold.prototype, 'id', { 602 | get: function() { 603 | return "fold-" + this.event + "-" + this.name; 604 | } 605 | }); 606 | 607 | Object.defineProperty(Log.Fold.prototype, 'span', { 608 | get: function() { 609 | return this.spans[0]; 610 | } 611 | }); 612 | 613 | Object.defineProperty(Log.Fold.prototype, 'data', { 614 | get: function() { 615 | return { 616 | type: 'fold', 617 | id: this.id, 618 | event: this.event, 619 | name: this.name 620 | }; 621 | } 622 | }); 623 | 624 | Log.prototype = Log.extend(new Log.Node, { 625 | set: function(num, string) { 626 | if (this.parts[num]) { 627 | return console.log("part " + num + " exists"); 628 | } else { 629 | this.parts[num] = true; 630 | return Log.Part.create(this, num, string); 631 | } 632 | }, 633 | insert: function(data, pos) { 634 | this.trigger('insert', data, pos); 635 | return this.renderer.insert(data, pos); 636 | }, 637 | remove: function(node) { 638 | this.trigger('remove', node); 639 | return this.renderer.remove(node); 640 | }, 641 | hide: function(node) { 642 | this.trigger('hide', node); 643 | return this.renderer.hide(node); 644 | }, 645 | trigger: function() { 646 | var args, ix, listener, _i, _len, _ref, _results; 647 | args = [this].concat(Array.prototype.slice.apply(arguments)); 648 | _ref = this.listeners; 649 | _results = []; 650 | for (ix = _i = 0, _len = _ref.length; _i < _len; ix = ++_i) { 651 | listener = _ref[ix]; 652 | _results.push(listener.notify.apply(listener, args)); 653 | } 654 | return _results; 655 | } 656 | }); 657 | 658 | Log.Listener = function() {}; 659 | 660 | Log.extend(Log.Listener.prototype, { 661 | notify: function(log, event) { 662 | if (this[event]) { 663 | return this[event].apply(this, [log].concat(Array.prototype.slice.call(arguments, 2))); 664 | } 665 | } 666 | }); 667 | 668 | Log.Folds = function(log) { 669 | this.log = log; 670 | this.folds = {}; 671 | return this; 672 | }; 673 | 674 | Log.extend(Log.Folds.prototype, { 675 | add: function(data) { 676 | var fold, _base, _name; 677 | fold = (_base = this.folds)[_name = data.name] || (_base[_name] = new Log.Folds.Fold); 678 | fold.receive(data, { 679 | autoCloseFold: this.log.autoCloseFold 680 | }); 681 | return fold.active; 682 | } 683 | }); 684 | 685 | Log.Folds.Fold = function() { 686 | return this; 687 | }; 688 | 689 | Log.extend(Log.Folds.Fold.prototype, { 690 | receive: function(data, options) { 691 | this[data.event] = data.id; 692 | if (this.start && this.end && !this.active) { 693 | return this.activate(options); 694 | } 695 | }, 696 | activate: function(options) { 697 | var fragment, nextSibling, node, parentNode, toRemove, _i, _len, _ref; 698 | options || (options = {}); 699 | if (Log.DEBUG) { 700 | console.log("F.n - activate " + this.start); 701 | } 702 | toRemove = this.fold.parentNode; 703 | parentNode = toRemove.parentNode; 704 | nextSibling = toRemove.nextSibling; 705 | parentNode.removeChild(toRemove); 706 | fragment = document.createDocumentFragment(); 707 | _ref = this.nodes; 708 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 709 | node = _ref[_i]; 710 | fragment.appendChild(node); 711 | } 712 | this.fold.appendChild(fragment); 713 | parentNode.insertBefore(toRemove, nextSibling); 714 | this.fold.setAttribute('class', this.classes(options['autoCloseFold'])); 715 | return this.active = true; 716 | }, 717 | classes: function(autoCloseFold) { 718 | var classes; 719 | classes = this.fold.getAttribute('class').split(' '); 720 | classes.push('fold'); 721 | if (!autoCloseFold) { 722 | classes.push('open'); 723 | } 724 | if (this.fold.childNodes.length > 2) { 725 | classes.push('active'); 726 | } 727 | return classes.join(' '); 728 | } 729 | }); 730 | 731 | Object.defineProperty(Log.Folds.Fold.prototype, 'fold', { 732 | get: function() { 733 | return this._fold || (this._fold = document.getElementById(this.start)); 734 | } 735 | }); 736 | 737 | Object.defineProperty(Log.Folds.Fold.prototype, 'nodes', { 738 | get: function() { 739 | var node, nodes; 740 | node = this.fold; 741 | nodes = []; 742 | while ((node = node.nextSibling) && node.id !== this.end) { 743 | nodes.push(node); 744 | } 745 | return nodes; 746 | } 747 | }); 748 | 749 | Log.Times = function(log) { 750 | this.log = log; 751 | this.times = {}; 752 | return this; 753 | }; 754 | 755 | Log.extend(Log.Times.prototype, { 756 | add: function(node) { 757 | var time, _base, _name; 758 | time = (_base = this.times)[_name = node.name] || (_base[_name] = new Log.Times.Time); 759 | return time.receive(node); 760 | }, 761 | duration: function(name) { 762 | if (this.times[name]) { 763 | return this.times[name].duration; 764 | } 765 | } 766 | }); 767 | 768 | Log.Times.Time = function() { 769 | return this; 770 | }; 771 | 772 | Log.extend(Log.Times.Time.prototype, { 773 | receive: function(node) { 774 | this[node.event] = node; 775 | if (Log.DEBUG) { 776 | console.log("T.0 - " + node.event + " " + node.name); 777 | } 778 | if (this.start && this.end) { 779 | return this.finish(); 780 | } 781 | }, 782 | finish: function() { 783 | var element; 784 | if (Log.DEBUG) { 785 | console.log("T.1 - finish " + this.start.name); 786 | } 787 | element = document.getElementById(this.start.id); 788 | if (element) { 789 | return this.update(element); 790 | } 791 | }, 792 | update: function(element) { 793 | element.setAttribute('class', 'duration'); 794 | element.setAttribute('title', "This command finished after " + this.duration + " seconds."); 795 | return element.lastChild.nodeValue = "" + this.duration + "s"; 796 | } 797 | }); 798 | 799 | Object.defineProperty(Log.Times.Time.prototype, 'duration', { 800 | get: function() { 801 | var duration; 802 | duration = this.stats.duration / 1000 / 1000 / 1000; 803 | return duration.toFixed(2); 804 | } 805 | }); 806 | 807 | Object.defineProperty(Log.Times.Time.prototype, 'stats', { 808 | get: function() { 809 | var stat, stats, _i, _len, _ref; 810 | if (!(this.end && this.end.stats)) { 811 | return {}; 812 | } 813 | stats = {}; 814 | _ref = this.end.stats.split(','); 815 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 816 | stat = _ref[_i]; 817 | stat = stat.split('='); 818 | stats[stat[0]] = stat[1]; 819 | } 820 | return stats; 821 | } 822 | }); 823 | 824 | Log.Deansi = { 825 | CLEAR_ANSI: /(?:\033)(?:\[0?c|\[[0356]n|\[7[lh]|\[\?25[lh]|\(B|H|\[(?:\d+(;\d+){,2})?G|\[(?:[12])?[JK]|[DM]|\[0K)/gm, 826 | apply: function(string) { 827 | var nodes, 828 | _this = this; 829 | if (!string) { 830 | return []; 831 | } 832 | string = string.replace(this.CLEAR_ANSI, ''); 833 | nodes = ansiparse(string).map(function(part) { 834 | return _this.node(part); 835 | }); 836 | return nodes; 837 | }, 838 | node: function(part) { 839 | var classes, node; 840 | node = { 841 | type: 'span', 842 | text: part.text 843 | }; 844 | if (classes = this.classes(part)) { 845 | node["class"] = classes.join(' '); 846 | } 847 | return node; 848 | }, 849 | classes: function(part) { 850 | var result; 851 | result = []; 852 | result = result.concat(this.colors(part)); 853 | if (result.length > 0) { 854 | return result; 855 | } 856 | }, 857 | colors: function(part) { 858 | var colors; 859 | colors = []; 860 | if (part.foreground) { 861 | colors.push(part.foreground); 862 | } 863 | if (part.background) { 864 | colors.push("bg-" + part.background); 865 | } 866 | if (part.bold) { 867 | colors.push('bold'); 868 | } 869 | if (part.italic) { 870 | colors.push('italic'); 871 | } 872 | if (part.underline) { 873 | colors.push('underline'); 874 | } 875 | return colors; 876 | }, 877 | hidden: function(part) { 878 | if (part.text.match(/\r/)) { 879 | part.text = part.text.replace(/^.*\r/gm, ''); 880 | return true; 881 | } 882 | } 883 | }; 884 | 885 | Log.Limit = function(max_lines) { 886 | this.max_lines = max_lines || 1000; 887 | return this; 888 | }; 889 | 890 | Log.Limit.prototype = Log.extend(new Log.Listener, { 891 | count: 0, 892 | insert: function(log, node, pos) { 893 | if (node.type === 'paragraph' && !node.hidden) { 894 | return this.count += 1; 895 | } 896 | } 897 | }); 898 | 899 | Object.defineProperty(Log.Limit.prototype, 'limited', { 900 | get: function() { 901 | return this.count >= this.max_lines; 902 | } 903 | }); 904 | 905 | Log.Renderer = function() { 906 | this.frag = document.createDocumentFragment(); 907 | this.para = this.createParagraph(); 908 | this.span = this.createSpan(); 909 | this.text = document.createTextNode(''); 910 | this.fold = this.createFold(); 911 | return this; 912 | }; 913 | 914 | Log.extend(Log.Renderer.prototype, { 915 | insert: function(data, pos) { 916 | var after, before, into, node; 917 | node = this.render(data); 918 | if (into = pos != null ? pos.into : void 0) { 919 | if (typeof into === 'String') { 920 | into = document.getElementById(pos != null ? pos.into : void 0); 921 | } 922 | if (pos != null ? pos.prepend : void 0) { 923 | this.prependTo(node, into); 924 | } else { 925 | this.appendTo(node, into); 926 | } 927 | } else if (after = pos != null ? pos.after : void 0) { 928 | if (typeof after === 'String') { 929 | after = document.getElementById(pos); 930 | } 931 | this.insertAfter(node, after); 932 | } else if (before = pos != null ? pos.before : void 0) { 933 | if (typeof before === 'String') { 934 | before = document.getElementById(pos != null ? pos.before : void 0); 935 | } 936 | this.insertBefore(node, before); 937 | } else { 938 | this.insertBefore(node); 939 | } 940 | return node; 941 | }, 942 | hide: function(node) { 943 | node.setAttribute('class', this.addClass(node.getAttribute('class'), 'hidden')); 944 | return node; 945 | }, 946 | remove: function(node) { 947 | if (node) { 948 | node.parentNode.removeChild(node); 949 | } 950 | return node; 951 | }, 952 | render: function(data) { 953 | var frag, node, type, _i, _len; 954 | if (data instanceof Array) { 955 | frag = this.frag.cloneNode(true); 956 | for (_i = 0, _len = data.length; _i < _len; _i++) { 957 | node = data[_i]; 958 | node = this.render(node); 959 | if (node) { 960 | frag.appendChild(node); 961 | } 962 | } 963 | return frag; 964 | } else { 965 | data.type || (data.type = 'paragraph'); 966 | type = data.type[0].toUpperCase() + data.type.slice(1); 967 | return this["render" + type](data); 968 | } 969 | }, 970 | renderParagraph: function(data) { 971 | var node, para, type, _i, _len, _ref; 972 | para = this.para.cloneNode(true); 973 | if (data.id) { 974 | para.setAttribute('id', data.id); 975 | } 976 | if (data.hidden) { 977 | para.setAttribute('style', 'display: none;'); 978 | } 979 | _ref = data.nodes || []; 980 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 981 | node = _ref[_i]; 982 | type = node.type[0].toUpperCase() + node.type.slice(1); 983 | node = this["render" + type](node); 984 | para.appendChild(node); 985 | } 986 | return para; 987 | }, 988 | renderFold: function(data) { 989 | var fold; 990 | fold = this.fold.cloneNode(true); 991 | fold.setAttribute('id', data.id || ("fold-" + data.event + "-" + data.name)); 992 | fold.setAttribute('class', "fold-" + data.event); 993 | if (data.event === 'start') { 994 | fold.lastChild.lastChild.nodeValue = data.name; 995 | } else { 996 | fold.removeChild(fold.lastChild); 997 | } 998 | return fold; 999 | }, 1000 | renderSpan: function(data) { 1001 | var span; 1002 | span = this.span.cloneNode(true); 1003 | if (data.id) { 1004 | span.setAttribute('id', data.id); 1005 | } 1006 | if (data["class"]) { 1007 | span.setAttribute('class', data["class"]); 1008 | } 1009 | span.lastChild.nodeValue = data.text || ''; 1010 | return span; 1011 | }, 1012 | renderText: function(data) { 1013 | var text; 1014 | text = this.text.cloneNode(true); 1015 | text.nodeValue = data.text; 1016 | return text; 1017 | }, 1018 | createParagraph: function() { 1019 | var para; 1020 | para = document.createElement('p'); 1021 | para.appendChild(document.createElement('a')); 1022 | return para; 1023 | }, 1024 | createFold: function() { 1025 | var fold; 1026 | fold = document.createElement('div'); 1027 | fold.appendChild(this.createSpan()); 1028 | fold.lastChild.setAttribute('class', 'fold-name'); 1029 | return fold; 1030 | }, 1031 | createSpan: function() { 1032 | var span; 1033 | span = document.createElement('span'); 1034 | span.appendChild(document.createTextNode(' ')); 1035 | return span; 1036 | }, 1037 | insertBefore: function(node, other) { 1038 | var log; 1039 | if (other) { 1040 | return other.parentNode.insertBefore(node, other); 1041 | } else { 1042 | log = document.getElementById('log'); 1043 | return log.insertBefore(node, log.firstChild); 1044 | } 1045 | }, 1046 | insertAfter: function(node, other) { 1047 | if (other.nextSibling) { 1048 | return this.insertBefore(node, other.nextSibling); 1049 | } else { 1050 | return this.appendTo(node, other.parentNode); 1051 | } 1052 | }, 1053 | prependTo: function(node, other) { 1054 | if (other.firstChild) { 1055 | return other.insertBefore(node, other.firstChild); 1056 | } else { 1057 | return appendTo(node, other); 1058 | } 1059 | }, 1060 | appendTo: function(node, other) { 1061 | return other.appendChild(node); 1062 | }, 1063 | addClass: function(classes, string) { 1064 | if (classes != null ? classes.indexOf(string) : void 0) { 1065 | return; 1066 | } 1067 | if (classes) { 1068 | return "" + classes + " " + string; 1069 | } else { 1070 | return string; 1071 | } 1072 | } 1073 | }); 1074 | 1075 | window.Log = Log; 1076 | -------------------------------------------------------------------------------- /vendor/rangeslider.css: -------------------------------------------------------------------------------- 1 | /*Range Slider Bar Time*/ 2 | .vjs-default-skin .vjs-timebar-RS{ 3 | color: red; 4 | top: -1em; 5 | height: 100%; 6 | position: relative; 7 | background: rgba(100,100,100,.5);/*Quitar*/ 8 | } 9 | 10 | 11 | 12 | /*Selection Range Slider Bar Selected*/ 13 | .vjs-default-skin .vjs-rangeslider-holder{height: 100%;} 14 | 15 | .vjs-default-skin .vjs-selectionbar-RS{ 16 | height: 100%; 17 | float: left; 18 | width: 100%; 19 | left: 0em; 20 | right: 0em; 21 | position:absolute; 22 | background-color: #FFE800; 23 | background: #FFE800; 24 | background: -moz-linear-gradient(top, #FFE800, #A69700); 25 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FFE800), to(#A69700)); 26 | background: -webkit-linear-gradient(top, #FFE800, #A69700); 27 | background: -o-linear-gradient(top, #FFE800, #A69700); 28 | background: -ms-linear-gradient(top, #FFE800, #A69700); 29 | background: linear-gradient(top, #FFE800, #A69700); 30 | opacity: 0.8; 31 | } 32 | 33 | .vjs-default-skin div.vjs-rangeslider-holder.locked > div.vjs-selectionbar-RS { 34 | background-color: #FF6565; 35 | background: #FF6565; 36 | background: -moz-linear-gradient(top, #FF6565, #300000); 37 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#FF6565), to(#300000)); 38 | background: -webkit-linear-gradient(top, #FF6565, #300000); 39 | background: -o-linear-gradient(top, #FF6565, #300000); 40 | background: -ms-linear-gradient(top, #FF6565, #300000); 41 | background: linear-gradient(top, #FF6565, #300000); 42 | } 43 | 44 | 45 | /*Arrow and Handle*/ 46 | .vjs-default-skin div.vjs-rangeslider-handle { 47 | position: absolute; 48 | margin-top: 0; 49 | cursor: pointer !important; 50 | background-color: transparent; 51 | } 52 | 53 | .vjs-default-skin .vjs-selectionbar-left-RS{height: 100%;left: 0;z-index:10} 54 | .vjs-default-skin .vjs-selectionbar-right-RS{height: 100%;left: 100%;z-index:20} 55 | 56 | .vjs-default-skin div.vjs-selectionbar-left-RS, 57 | .vjs-default-skin div.vjs-selectionbar-right-RS { 58 | top: 0em; 59 | position: absolute; 60 | width:0em; 61 | } 62 | 63 | .vjs-default-skin div.vjs-selectionbar-arrow-RS { 64 | width: 0; 65 | height: 0; 66 | border-left: 1em solid transparent; 67 | border-right: 1em solid transparent; 68 | border-top: 1em solid #FFF273; 69 | margin-left: -1em; 70 | opacity: 0.8; 71 | 72 | position: absolute; 73 | top: -1em; 74 | } 75 | .vjs-default-skin div.vjs-rangeslider-handle.active > div.vjs-selectionbar-arrow-RS { 76 | border-top-color: #5F5FB3; 77 | } 78 | 79 | .vjs-default-skin div.vjs-rangeslider-holder.locked .vjs-rangeslider-handle > div.vjs-selectionbar-arrow-RS { 80 | border-top-color: #FF6565; 81 | } 82 | 83 | .vjs-default-skin div.vjs-selectionbar-line-RS { 84 | width: 1px; 85 | height: 1em; 86 | background-color: #FFF273; 87 | 88 | position:absolute; 89 | top: 0em; 90 | } 91 | .vjs-default-skin div.vjs-rangeslider-handle.active > div.vjs-selectionbar-line-RS { 92 | background-color: #5F5FB3; 93 | } 94 | 95 | .vjs-default-skin div.vjs-rangeslider-holder.locked .vjs-rangeslider-handle > div.vjs-selectionbar-line-RS { 96 | background-color: #FF6565; 97 | } 98 | 99 | 100 | /* Time Panel */ 101 | .vjs-default-skin .vjs-timepanel-RS{ 102 | width: 100%; 103 | height: 1em; 104 | font-weight: bold; 105 | font-size: 15px; 106 | top: -2em; 107 | position: absolute; 108 | visibility:visible; 109 | opacity:1; 110 | transition-delay:0s; 111 | } 112 | .vjs-default-skin .vjs-timepanel-RS.disable{ 113 | visibility:hidden; 114 | opacity:0; 115 | -webkit-transition: visibility 1s linear 1s,opacity 1s linear; 116 | -moz-transition: visibility 1s linear 1s,opacity 1s linear; 117 | -o-transition: visibility 1s linear 1s,opacity 1s linear; 118 | transition:visibility 1s linear 1s,opacity 1s linear; 119 | } 120 | 121 | .vjs-default-skin .vjs-timepanel-left-RS, 122 | .vjs-default-skin .vjs-timepanel-right-RS{ 123 | font-weight: normal; 124 | font-size: 1em; 125 | color: #666666; 126 | border: 1px solid #666666; 127 | background-color: white; 128 | border-radius: 5px; 129 | position: absolute; 130 | height:116%; 131 | padding-right: 0.3em; 132 | padding-left: 0.3em; 133 | } 134 | .vjs-default-skin .vjs-timepanel-left-RS{ 135 | left:0.5% 136 | } 137 | .vjs-default-skin .vjs-timepanel-right-RS{ 138 | left:92% 139 | } 140 | 141 | 142 | 143 | 144 | /* Control Time Panel */ 145 | .vjs-default-skin .vjs-controltimepanel-RS{ 146 | width: 18em; 147 | font-size: 1em; 148 | line-height: 3em; 149 | } 150 | 151 | .vjs-default-skin .vjs-controltimepanel-RS input{ 152 | width: 1.5em; 153 | background: rgba(102, 168, 204, 0.16); 154 | border: 1px solid transparent; 155 | color: black; 156 | font-size: 1em; 157 | margin-left: 2px; 158 | text-align: center; 159 | color: white; 160 | } 161 | 162 | .vjs-default-skin .vjs-controltimepanel-left-RS{ 163 | width: 50%; 164 | float: left; 165 | } 166 | .vjs-default-skin .vjs-controltimepanel-right-RS{ 167 | float:right; 168 | width: 48%; 169 | } 170 | .vjs-default-skin .vjs-controltimepanel-RS input{ 171 | margin: 0; 172 | padding: 0; 173 | display: table-cell; 174 | } 175 | 176 | 177 | /* ---------------- Video-js plugin ---------------- */ 178 | 179 | .vjs-default-skin *, .vjs-default-skin *:before, .vjs-default-skin *:after { 180 | -moz-box-sizing: border-box; 181 | -webkit-box-sizing: border-box; 182 | box-sizing: border-box; 183 | margin: 0; 184 | padding: 0; 185 | } 186 | 187 | -------------------------------------------------------------------------------- /vendor/rangeslider.js: -------------------------------------------------------------------------------- 1 | /* 2 | RangeSlider v1.1 (https://github.com/danielcebrian/rangeslider-videojs) 3 | Copyright (C) 2014 Daniel Cebrián Robles 4 | License GNU: 5 | https://github.com/danielcebrian/rangeslider-videojs/blob/master/License-GNU.rst 6 | License Apache: 7 | https://github.com/danielcebrian/rangeslider-videojs/blob/master/License-Apache.rst 8 | */ 9 | 10 | (function() { 11 | 'use strict'; 12 | 13 | function blockTextSelection() { 14 | document.body.focus(); 15 | document.onselectstart = function() { return false; }; 16 | } 17 | function hasElClass(element, classToCheck) { 18 | return ((' ' + element.className + ' ') 19 | .indexOf(' ' + classToCheck + ' ') !== -1); 20 | } 21 | function addElClass(element, classToAdd) { 22 | if (!hasElClass(element, classToAdd)) { 23 | element.className = element.className === '' 24 | ? classToAdd 25 | : element.className + ' ' + classToAdd; 26 | } 27 | } 28 | function removeElClass(element, classToRemove) { 29 | if (!hasElClass(element, classToRemove)) { 30 | return; 31 | } 32 | 33 | var classNames = element.className.split(' '); 34 | 35 | // no arr.indexOf in ie8, and we don't want to add a big shim 36 | for (var i = classNames.length - 1; i >= 0; i--) { 37 | if (classNames[i] === classToRemove) { 38 | classNames.splice(i, 1); 39 | } 40 | } 41 | 42 | element.className = classNames.join(' '); 43 | } 44 | function findElPosition(el) { 45 | var box; 46 | 47 | if (el.getBoundingClientRect && el.parentNode) { 48 | box = el.getBoundingClientRect(); 49 | } 50 | 51 | if (!box) { 52 | return {left : 0, top : 0}; 53 | } 54 | 55 | const docEl = document.documentElement; 56 | const body = document.body; 57 | 58 | const clientLeft = docEl.clientLeft || body.clientLeft || 0; 59 | const scrollLeft = window.pageXOffset || body.scrollLeft; 60 | const left = box.left + scrollLeft - clientLeft; 61 | 62 | const clientTop = docEl.clientTop || body.clientTop || 0; 63 | const scrollTop = window.pageYOffset || body.scrollTop; 64 | const top = box.top + scrollTop - clientTop; 65 | 66 | // Android sometimes returns slightly off decimal values, so need to round 67 | return {left : Math.round(left), top : Math.round(top)}; 68 | } 69 | 70 | //-- Load RangeSlider plugin in videojs 71 | function RangeSlider_(options) { 72 | var player = this; 73 | 74 | player.rangeslider = new RangeSlider(player, options); 75 | 76 | // When the DOM and the video media is loaded 77 | function initialVideoFinished(event) { 78 | var plugin = player.rangeslider; 79 | // All components will be initialize after they have been loaded by 80 | // videojs 81 | for (var index in plugin.components) { 82 | plugin.components[index].init_(); 83 | } 84 | 85 | if (plugin.options.hidden) 86 | plugin.hide(); // Hide the Range Slider 87 | 88 | if (plugin.options.locked) 89 | plugin.lock(); // Lock the Range Slider 90 | 91 | if (plugin.options.panel == false) 92 | plugin.hidePanel(); // Hide the second Panel 93 | 94 | if (plugin.options.controlTime == false) 95 | plugin.hidecontrolTime(); // Hide the control time panel 96 | 97 | plugin._reset(); 98 | player.trigger( 99 | 'loadedRangeSlider'); // Let know if the Range Slider DOM is ready 100 | } 101 | if (player.techName == 'Youtube') { 102 | // Detect youtube problems 103 | player.one('error', function(e) { 104 | switch (player.error) { 105 | case 2: 106 | alert( 107 | "The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks."); 108 | case 5: 109 | alert( 110 | "The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred."); 111 | case 100: 112 | alert( 113 | "The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private."); 114 | break; 115 | case 101: 116 | alert( 117 | "The owner of the requested video does not allow it to be played in embedded players."); 118 | break; 119 | case 150: 120 | alert( 121 | "The owner of the requested video does not allow it to be played in embedded players."); 122 | break; 123 | default: 124 | alert("Unknown Error"); 125 | break; 126 | } 127 | }); 128 | player.on('firstplay', initialVideoFinished); 129 | } else { 130 | player.one('playing', initialVideoFinished); 131 | } 132 | 133 | console.log("Loaded Plugin RangeSlider"); 134 | } 135 | videojs.plugin('rangeslider', RangeSlider_); 136 | 137 | //-- Plugin 138 | function RangeSlider(player, options) { 139 | var player = player || this; 140 | 141 | this.player = player; 142 | 143 | this.components = {}; // holds any custom components we add to the player 144 | 145 | options = options || {}; // plugin options 146 | 147 | if (!options.hasOwnProperty('locked')) 148 | options.locked = false; // lock slider handles 149 | 150 | if (!options.hasOwnProperty('hidden')) 151 | options.hidden = false; // hide slider handles 152 | 153 | if (!options.hasOwnProperty('panel')) 154 | options.panel = true; // Show Second Panel 155 | 156 | if (!options.hasOwnProperty('controlTime')) 157 | options.controlTime = 158 | true; // Show Control Time to set the arrows in the edition 159 | 160 | this.options = options; 161 | 162 | this.init(); 163 | } 164 | 165 | //-- Methods 166 | RangeSlider.prototype = { 167 | /*Constructor*/ 168 | init : function() { 169 | var player = this.player || {}; 170 | 171 | this.updatePrecision = 3; 172 | 173 | // position in second of the arrows 174 | this.start = 0; 175 | this.end = 0; 176 | 177 | // components of the plugin 178 | var controlBar = player.controlBar; 179 | var seekBar = controlBar.progressControl.seekBar; 180 | this.components.RSTimeBar = seekBar.RSTimeBar; 181 | this.components.ControlTimePanel = controlBar.ControlTimePanel; 182 | 183 | // Save local component 184 | this.rstb = this.components.RSTimeBar; 185 | this.box = this.components.SeekRSBar = this.rstb.SeekRSBar; 186 | this.bar = this.components.SelectionBar = this.box.SelectionBar; 187 | this.left = this.components.SelectionBarLeft = this.box.SelectionBarLeft; 188 | this.right = this.components.SelectionBarRight = 189 | this.box.SelectionBarRight; 190 | this.tp = this.components.TimePanel = this.box.TimePanel; 191 | this.tpl = this.components.TimePanelLeft = this.tp.TimePanelLeft; 192 | this.tpr = this.components.TimePanelRight = this.tp.TimePanelRight; 193 | this.ctp = this.components.ControlTimePanel; 194 | this.ctpl = this.components.ControlTimePanelLeft = 195 | this.ctp.ControlTimePanelLeft; 196 | this.ctpr = this.components.ControlTimePanelRight = 197 | this.ctp.ControlTimePanelRight; 198 | 199 | }, 200 | lock : function() { 201 | this.options.locked = true; 202 | this.ctp.enable(false); 203 | if (typeof this.box != 'undefined') 204 | addElClass(this.box.el_, 'locked'); 205 | }, 206 | unlock : function() { 207 | this.options.locked = false; 208 | this.ctp.enable(); 209 | if (typeof this.box != 'undefined') 210 | removeElClass(this.box.el_, 'locked'); 211 | }, 212 | show : function() { 213 | this.options.hidden = false; 214 | if (typeof this.rstb != 'undefined') { 215 | this.rstb.show(); 216 | if (this.options.controlTime) 217 | this.showcontrolTime(); 218 | } 219 | }, 220 | hide : function() { 221 | this.options.hidden = true; 222 | if (typeof this.rstb != 'undefined') { 223 | this.rstb.hide(); 224 | this.ctp.hide(); 225 | } 226 | }, 227 | showPanel : function() { 228 | this.options.panel = true; 229 | if (typeof this.tp != 'undefined') 230 | removeElClass(this.tp.el_, 'disable'); 231 | }, 232 | hidePanel : function() { 233 | this.options.panel = false; 234 | if (typeof this.tp != 'undefined') 235 | addElClass(this.tp.el_, 'disable'); 236 | }, 237 | showcontrolTime : function() { 238 | this.options.controlTime = true; 239 | if (typeof this.ctp != 'undefined') 240 | this.ctp.show(); 241 | }, 242 | hidecontrolTime : function() { 243 | this.options.controlTime = false; 244 | if (typeof this.ctp != 'undefined') 245 | this.ctp.hide(); 246 | }, 247 | setValue : function(index, seconds, writeControlTime) { 248 | // index = 0 for the left Arrow and 1 for the right Arrow. Value in 249 | // seconds 250 | var writeControlTime = 251 | typeof writeControlTime != 'undefined' ? writeControlTime : true; 252 | 253 | var percent = this._percent(seconds); 254 | var isValidIndex = (index === 0 || index === 1); 255 | var isChangeable = !this.locked; 256 | if (isChangeable && isValidIndex) 257 | this.box.setPosition(index, percent, writeControlTime); 258 | }, 259 | setValues : function(start, end, writeControlTime) { 260 | // index = 0 for the left Arrow and 1 for the right Arrow. Value in 261 | // seconds 262 | var writeControlTime = 263 | typeof writeControlTime != 'undefined' ? writeControlTime : true; 264 | 265 | this._reset(); 266 | 267 | this._setValuesLocked(start, end, writeControlTime); 268 | }, 269 | getValues : function() { // get values in seconds 270 | var values = {}, start, end; 271 | start = this.start || this._getArrowValue(0); 272 | end = this.end || this._getArrowValue(1); 273 | return {start : start, end : end}; 274 | }, 275 | playBetween : function(start, end, showRS) { 276 | showRS = typeof showRS == 'undefined' ? true : showRS; 277 | this.player.currentTime(start); 278 | this.player.play(); 279 | if (showRS) { 280 | this.show(); 281 | this._reset(); 282 | } else { 283 | this.hide(); 284 | } 285 | this._setValuesLocked(start, end); 286 | 287 | this.bar.activatePlay(start, end); 288 | }, 289 | loop : function(start, end, show) { 290 | var player = this.player; 291 | 292 | if (player) { 293 | player.on("pause", 294 | videojs.bind(this, function() { this.looping = false; })); 295 | 296 | show = typeof show === 'undefined' ? true : show; 297 | 298 | if (show) { 299 | this.show(); 300 | this._reset(); 301 | } else { 302 | this.hide(); 303 | } 304 | this._setValuesLocked(start, end); 305 | 306 | this.timeStart = start; 307 | this.timeEnd = end; 308 | this.looping = true; 309 | 310 | this.player.currentTime(start); 311 | this.player.play(); 312 | 313 | this.player.on("timeupdate", videojs.bind(this, this.bar.process_loop)); 314 | } 315 | }, 316 | _getArrowValue : function(index) { 317 | var index = index || 0; 318 | var duration = this.player.duration(); 319 | 320 | duration = typeof duration == 'undefined' ? 0 : duration; 321 | 322 | var percentage = 323 | this[index === 0 ? "left" : "right"].el_.style.left.replace("%", ""); 324 | if (percentage == "") 325 | percentage = index === 0 ? 0 : 100; 326 | 327 | return (this._seconds(percentage / 100)) 328 | .toFixed(this.updatePrecision - 1); 329 | }, 330 | _percent : function(seconds) { 331 | var duration = this.player.duration(); 332 | if (isNaN(duration)) { 333 | return 0; 334 | } 335 | return Math.min(1, Math.max(0, seconds / duration)); 336 | }, 337 | _seconds : function(percent) { 338 | var duration = this.player.duration(); 339 | if (isNaN(duration)) { 340 | return 0; 341 | } 342 | return Math.min(duration, Math.max(0, percent * duration)); 343 | }, 344 | _reset : function() { 345 | var duration = this.player.duration(); 346 | this.tpl.el_.style.left = '0%'; 347 | this.tpr.el_.style.left = '100%'; 348 | this._setValuesLocked(0, duration); 349 | }, 350 | _setValuesLocked : function(start, end, writeControlTime) { 351 | var triggerSliderChange = typeof writeControlTime != 'undefined'; 352 | var writeControlTime = 353 | typeof writeControlTime != 'undefined' ? writeControlTime : true; 354 | if (this.options.locked) { 355 | this.unlock(); // It is unlocked to change the bar position. In the end 356 | // it will return the value. 357 | this.setValue(0, start, writeControlTime); 358 | this.setValue(1, end, writeControlTime); 359 | this.lock(); 360 | } else { 361 | this.setValue(0, start, writeControlTime); 362 | this.setValue(1, end, writeControlTime); 363 | } 364 | 365 | // Trigger slider change 366 | if (triggerSliderChange) { 367 | this._triggerSliderChange(); 368 | } 369 | }, 370 | _checkControlTime : function(index, TextInput, timeOld) { 371 | var h = TextInput[0], m = TextInput[1], s = TextInput[2], 372 | newHour = h.value, newMin = m.value, newSec = s.value, obj, objNew, 373 | objOld; 374 | index = index || 0; 375 | 376 | if (newHour != timeOld[0]) { 377 | obj = h; 378 | objNew = newHour; 379 | objOld = timeOld[0]; 380 | } else if (newMin != timeOld[1]) { 381 | obj = m; 382 | objNew = newMin; 383 | objOld = timeOld[1]; 384 | } else if (newSec != timeOld[2]) { 385 | obj = s; 386 | objNew = newSec; 387 | objOld = timeOld[2]; 388 | } else { 389 | return false; 390 | } 391 | 392 | var duration = this.player.duration() || 0, durationSel; 393 | 394 | var intRegex = /^\d+$/; // check if the objNew is an integer 395 | if (!intRegex.test(objNew) || objNew > 60) { 396 | objNew = objNew == "" ? "" : objOld; 397 | } 398 | 399 | newHour = newHour == "" ? 0 : newHour; 400 | newMin = newMin == "" ? 0 : newMin; 401 | newSec = newSec == "" ? 0 : newSec; 402 | 403 | durationSel = videojs.TextTrack.prototype.parseCueTime( 404 | newHour + ":" + newMin + ":" + newSec); 405 | if (durationSel > duration) { 406 | obj.value = objOld; 407 | obj.style.border = "1px solid red"; 408 | } else { 409 | obj.value = objNew; 410 | h.style.border = m.style.border = s.style.border = 411 | "1px solid transparent"; 412 | this.setValue(index, durationSel, false); 413 | 414 | // Trigger slider change 415 | this._triggerSliderChange(); 416 | } 417 | if (index === 1) { 418 | var oldTimeLeft = this.ctpl.el_.children, 419 | durationSelLeft = videojs.TextTrack.prototype.parseCueTime( 420 | oldTimeLeft[0].value + ":" + oldTimeLeft[1].value + ":" + 421 | oldTimeLeft[2].value); 422 | if (durationSel < durationSelLeft) { 423 | obj.style.border = "1px solid red"; 424 | } 425 | } else { 426 | 427 | var oldTimeRight = this.ctpr.el_.children, 428 | durationSelRight = videojs.TextTrack.prototype.parseCueTime( 429 | oldTimeRight[0].value + ":" + oldTimeRight[1].value + ":" + 430 | oldTimeRight[2].value); 431 | if (durationSel > durationSelRight) { 432 | obj.style.border = "1px solid red"; 433 | } 434 | } 435 | }, 436 | _triggerSliderChange : function() { this.player.trigger("sliderchange"); } 437 | }; 438 | 439 | //----------------Public Functions----------------// 440 | 441 | //-- Public Functions added to video-js 442 | var videojsPlayer = videojs.getComponent('Player'); 443 | 444 | // Lock the Slider bar and it will not be possible to change the arrow 445 | // positions 446 | videojsPlayer.prototype.lockSlider = function() { 447 | return this.rangeslider.lock(); 448 | }; 449 | 450 | // Unlock the Slider bar and it will be possible to change the arrow positions 451 | videojsPlayer.prototype.unlockSlider = function() { 452 | return this.rangeslider.unlock(); 453 | }; 454 | 455 | // Show the Slider Bar Component 456 | videojsPlayer.prototype.showSlider = function() { 457 | return this.rangeslider.show(); 458 | }; 459 | 460 | // Hide the Slider Bar Component 461 | videojsPlayer.prototype.hideSlider = function() { 462 | return this.rangeslider.hide(); 463 | }; 464 | 465 | // Show the Panel with the seconds of the selection 466 | videojsPlayer.prototype.showSliderPanel = function() { 467 | return this.rangeslider.showPanel(); 468 | }; 469 | 470 | // Hide the Panel with the seconds of the selection 471 | videojsPlayer.prototype.hideSliderPanel = function() { 472 | return this.rangeslider.hidePanel(); 473 | }; 474 | 475 | // Show the control Time to edit the position of the arrows 476 | videojsPlayer.prototype.showControlTime = function() { 477 | return this.rangeslider.showcontrolTime(); 478 | }; 479 | 480 | // Hide the control Time to edit the position of the arrows 481 | videojsPlayer.prototype.hideControlTime = function() { 482 | return this.rangeslider.hidecontrolTime(); 483 | }; 484 | 485 | // Set a Value in second for both arrows 486 | videojsPlayer.prototype.setValueSlider = function(start, end) { 487 | return this.rangeslider.setValues(start, end); 488 | }; 489 | 490 | // The video will be played in a selected section 491 | videojsPlayer.prototype.playBetween = function(start, end) { 492 | return this.rangeslider.playBetween(start, end); 493 | }; 494 | 495 | // The video will loop between to values 496 | videojsPlayer.prototype.loopBetween = function(start, end) { 497 | return this.rangeslider.loop(start, end); 498 | }; 499 | 500 | // Set a Value in second for the arrows 501 | videojsPlayer.prototype.getValueSlider = function() { 502 | return this.rangeslider.getValues(); 503 | }; 504 | 505 | //----------------Create new Components----------------// 506 | 507 | //--Charge the new Component into videojs 508 | videojs.getComponent('SeekBar') 509 | .prototype.options_.children.push('RSTimeBar'); // Range Slider Time Bar 510 | videojs.getComponent('ControlBar') 511 | .prototype.options_.children.push( 512 | 'ControlTimePanel'); // Panel with the time of the range slider 513 | 514 | var Component = videojs.getComponent('Component'); 515 | //-- Design the new components 516 | 517 | /** 518 | * Range Slider Time Bar 519 | * @param {videojs.Player|Object} player 520 | * @param {Object=} options 521 | * @constructor 522 | */ 523 | videojs.RSTimeBar = videojs.extend(Component, { 524 | /** @constructor */ 525 | constructor : function(player, options) { 526 | Component.call(this, player, options); 527 | } 528 | }); 529 | 530 | videojs.RSTimeBar.prototype.init_ = function() { 531 | this.rs = this.player_.rangeslider; 532 | }; 533 | 534 | videojs.RSTimeBar.prototype.options_ = {children : {'SeekRSBar' : {}}}; 535 | 536 | videojs.RSTimeBar.prototype.createEl = function() { 537 | return Component.prototype.createEl.call( 538 | this, 'div', {className : 'vjs-timebar-RS', innerHTML : ''}); 539 | }; 540 | 541 | /** 542 | * Seek Range Slider Bar and holder for the selection bars 543 | * @param {videojs.Player|Object} player 544 | * @param {Object=} options 545 | * @constructor 546 | */ 547 | videojs.SeekRSBar = videojs.extend(videojs.getComponent('Component'), { 548 | /** @constructor */ 549 | constructor : function(player, options) { 550 | Component.call(this, player, options); 551 | this.on('mousedown', this.onMouseDown); 552 | } 553 | }); 554 | 555 | videojs.SeekRSBar.prototype.init_ = function() { 556 | this.rs = this.player_.rangeslider; 557 | }; 558 | 559 | videojs.SeekRSBar.prototype.options_ = { 560 | children : { 561 | 'SelectionBar' : {}, 562 | 'SelectionBarLeft' : {}, 563 | 'SelectionBarRight' : {}, 564 | 'TimePanel' : {}, 565 | } 566 | }; 567 | 568 | videojs.SeekRSBar.prototype.createEl = function() { 569 | return Component.prototype.createEl.call( 570 | this, 'div', {className : 'vjs-rangeslider-holder'}); 571 | }; 572 | 573 | videojs.SeekRSBar.prototype.onMouseDown = function(event) { 574 | event.preventDefault(); 575 | blockTextSelection(); 576 | 577 | if (!this.rs.options.locked) { 578 | videojs.on(document, "mousemove", videojs.bind(this, this.onMouseMove)); 579 | videojs.on(document, "mouseup", videojs.bind(this, this.onMouseUp)); 580 | } 581 | }; 582 | 583 | videojs.SeekRSBar.prototype.onMouseUp = function(event) { 584 | videojs.off(document, "mousemove", this.onMouseMove, false); 585 | videojs.off(document, "mouseup", this.onMouseUp, false); 586 | }; 587 | 588 | videojs.SeekRSBar.prototype.onMouseMove = function(event) { 589 | var left = this.calculateDistance(event); 590 | 591 | if (this.rs.left.pressed) 592 | this.setPosition(0, left); 593 | else if (this.rs.right.pressed) 594 | this.setPosition(1, left); 595 | 596 | this.player_.currentTime(this.rs._seconds(left)); 597 | 598 | // Trigger slider change 599 | if (this.rs.left.pressed || this.rs.right.pressed) { 600 | this.rs._triggerSliderChange(); 601 | } 602 | }; 603 | 604 | videojs.SeekRSBar.prototype.setPosition = function(index, left, 605 | writeControlTime) { 606 | var writeControlTime = 607 | typeof writeControlTime != 'undefined' ? writeControlTime : true; 608 | // index = 0 for left side, index = 1 for right side 609 | var index = index || 0; 610 | 611 | // Position shouldn't change when handle is locked 612 | if (this.rs.options.locked) 613 | return false; 614 | 615 | // Check for invalid position 616 | if (isNaN(left)) 617 | return false; 618 | 619 | // Check index between 0 and 1 620 | if (!(index === 0 || index === 1)) 621 | return false; 622 | 623 | // Alias 624 | var ObjLeft = this.rs.left.el_, ObjRight = this.rs.right.el_, 625 | Obj = this.rs[index === 0 ? 'left' : 'right'].el_, 626 | tpr = this.rs.tpr.el_, tpl = this.rs.tpl.el_, bar = this.rs.bar, 627 | ctp = this.rs[index === 0 ? 'ctpl' : 'ctpr'].el_; 628 | 629 | // Check if left arrow is passing the right arrow 630 | if ((index === 0 ? bar.updateLeft(left) : bar.updateRight(left))) { 631 | Obj.style.left = (left * 100) + '%'; 632 | index === 0 ? bar.updateLeft(left) : bar.updateRight(left); 633 | 634 | this.rs[index === 0 ? 'start' : 'end'] = this.rs._seconds(left); 635 | 636 | // Fix the problem when you press the button and the two arrow are 637 | // underhand 638 | // left.zIndex = 10 and right.zIndex=20. This is always less in this case: 639 | if (index === 0) { 640 | if ((left) >= 0.9) 641 | ObjLeft.style.zIndex = 25; 642 | else 643 | ObjLeft.style.zIndex = 10; 644 | } 645 | 646 | //-- Panel 647 | var TimeText = videojs.formatTime(this.rs._seconds(left)), 648 | tplTextLegth = tpl.children[0].innerHTML.length; 649 | var MaxP, MinP, MaxDisP; 650 | if (tplTextLegth <= 4) // 0:00 651 | MaxDisP = this.player_.isFullScreen ? 3.25 : 6.5; 652 | else if (tplTextLegth <= 5) // 00:00 653 | MaxDisP = this.player_.isFullScreen ? 4 : 8; 654 | else // 0:00:00 655 | MaxDisP = this.player_.isFullScreen ? 5 : 10; 656 | if (TimeText.length <= 4) { // 0:00 657 | MaxP = this.player_.isFullScreen ? 97 : 93; 658 | MinP = this.player_.isFullScreen ? 0.1 : 0.5; 659 | } else if (TimeText.length <= 5) { // 00:00 660 | MaxP = this.player_.isFullScreen ? 96 : 92; 661 | MinP = this.player_.isFullScreen ? 0.1 : 0.5; 662 | } else { // 0:00:00 663 | MaxP = this.player_.isFullScreen ? 95 : 91; 664 | MinP = this.player_.isFullScreen ? 0.1 : 0.5; 665 | } 666 | 667 | if (index === 0) { 668 | tpl.style.left = 669 | Math.max(MinP, Math.min(MaxP, (left * 100 - MaxDisP / 2))) + '%'; 670 | 671 | if ((tpr.style.left.replace("%", "") - 672 | tpl.style.left.replace("%", "")) <= MaxDisP) 673 | tpl.style.left = 674 | Math.max(MinP, Math.min(MaxP, tpr.style.left.replace("%", "") - 675 | MaxDisP)) + 676 | '%'; 677 | tpl.children[0].innerHTML = TimeText; 678 | } else { 679 | tpr.style.left = 680 | Math.max(MinP, Math.min(MaxP, (left * 100 - MaxDisP / 2))) + '%'; 681 | 682 | if (((tpr.style.left.replace("%", "") || 100) - 683 | tpl.style.left.replace("%", "")) <= MaxDisP) 684 | tpr.style.left = 685 | Math.max(MinP, Math.min(MaxP, tpl.style.left.replace("%", "") - 686 | 0 + MaxDisP)) + 687 | '%'; 688 | tpr.children[0].innerHTML = TimeText; 689 | } 690 | //-- Control Time 691 | if (writeControlTime) { 692 | var time = TimeText.split(":"), h, m, s; 693 | if (time.length == 2) { 694 | h = 0; 695 | m = time[0]; 696 | s = time[1]; 697 | } else { 698 | h = time[0]; 699 | m = time[1]; 700 | s = time[2]; 701 | } 702 | ctp.children[0].value = h; 703 | ctp.children[1].value = m; 704 | ctp.children[2].value = s; 705 | } 706 | } 707 | return true; 708 | }; 709 | 710 | videojs.SeekRSBar.prototype.calculateDistance = function(event) { 711 | var rstbX = this.getRSTBX(); 712 | var rstbW = this.getRSTBWidth(); 713 | var handleW = this.getWidth(); 714 | 715 | // Adjusted X and Width, so handle doesn't go outside the bar 716 | rstbX = rstbX + (handleW / 2); 717 | rstbW = rstbW - handleW; 718 | 719 | // Percent that the click is through the adjusted area 720 | return Math.max(0, Math.min(1, (event.pageX - rstbX) / rstbW)); 721 | }; 722 | 723 | videojs.SeekRSBar.prototype.getRSTBWidth = function() { 724 | return this.el_.offsetWidth; 725 | }; 726 | videojs.SeekRSBar.prototype.getRSTBX = function() { 727 | return findElPosition(this.el_).left; 728 | }; 729 | videojs.SeekRSBar.prototype.getWidth = function() { 730 | return this.rs.left.el_.offsetWidth; // does not matter left or right 731 | }; 732 | 733 | /** 734 | * This is the bar with the selection of the RangeSlider 735 | * @param {videojs.Player|Object} player 736 | * @param {Object=} options 737 | * @constructor 738 | */ 739 | videojs.SelectionBar = videojs.extend(videojs.getComponent('Component'), { 740 | /** @constructor */ 741 | constructor : function(player, options) { 742 | Component.call(this, player, options); 743 | this.on('mouseup', this.onMouseUp); 744 | this.fired = false; 745 | } 746 | }); 747 | 748 | videojs.SelectionBar.prototype.init_ = function() { 749 | this.rs = this.player_.rangeslider; 750 | }; 751 | 752 | videojs.SelectionBar.prototype.createEl = function() { 753 | return Component.prototype.createEl.call( 754 | this, 'div', {className : 'vjs-selectionbar-RS'}); 755 | }; 756 | 757 | videojs.SelectionBar.prototype.onMouseUp = function() { 758 | var start = this.rs.left.el_.style.left.replace("%", ""), 759 | end = this.rs.right.el_.style.left.replace("%", ""), 760 | duration = this.player_.duration(), precision = this.rs.updatePrecision, 761 | segStart = (start * duration / 100).toFixed(precision), 762 | segEnd = (end * duration / 100).toFixed(precision); 763 | this.player_.currentTime(segStart); 764 | this.player_.play(); 765 | this.rs.bar.activatePlay(segStart, segEnd); 766 | }; 767 | 768 | videojs.SelectionBar.prototype.updateLeft = function(left) { 769 | var rightVal = 770 | this.rs.right.el_.style.left != '' ? this.rs.right.el_.style.left : 100; 771 | var right = parseFloat(rightVal) / 100; 772 | 773 | var width = 774 | (right - left) 775 | .toFixed(this.rs.updatePrecision); // round necessary for not get 776 | // 0.6e-7 for example that 777 | // it's not able for the html 778 | // css width 779 | 780 | //(right+0.00001) is to fix the precision of the css in html 781 | if (left <= (right + 0.00001)) { 782 | this.rs.bar.el_.style.left = (left * 100) + '%'; 783 | this.rs.bar.el_.style.width = (width * 100) + '%'; 784 | return true; 785 | } 786 | return false; 787 | }; 788 | 789 | videojs.SelectionBar.prototype.updateRight = function(right) { 790 | var leftVal = 791 | this.rs.left.el_.style.left != '' ? this.rs.left.el_.style.left : 0; 792 | var left = parseFloat(leftVal) / 100; 793 | 794 | var width = 795 | (right - left) 796 | .toFixed(this.rs.updatePrecision); // round necessary for not get 797 | // 0.6e-7 for example that 798 | // it's not able for the html 799 | // css width 800 | 801 | //(right+0.00001) is to fix the precision of the css in html 802 | if ((right + 0.00001) >= left) { 803 | this.rs.bar.el_.style.width = (width * 100) + '%'; 804 | this.rs.bar.el_.style.left = ((right - width) * 100) + '%'; 805 | return true; 806 | } 807 | return false; 808 | }; 809 | 810 | videojs.SelectionBar.prototype.activatePlay = function(start, end) { 811 | this.timeStart = start; 812 | this.timeEnd = end; 813 | 814 | this.suspendPlay(); 815 | 816 | this.player_.on("timeupdate", videojs.bind(this, this._processPlay)); 817 | }; 818 | 819 | videojs.SelectionBar.prototype.suspendPlay = function() { 820 | this.fired = false; 821 | this.player_.off("timeupdate", videojs.bind(this, this._processPlay)); 822 | }; 823 | 824 | videojs.SelectionBar.prototype._processPlay = function() { 825 | // Check if current time is between start and end 826 | if (this.player_.currentTime() >= this.timeStart && 827 | (this.timeEnd < 0 || this.player_.currentTime() < this.timeEnd)) { 828 | if (this.fired) { // Do nothing if start has already been called 829 | return; 830 | } 831 | this.fired = true; // Set fired flag to true 832 | } else { 833 | if (!this.fired) { // Do nothing if end has already been called 834 | return; 835 | } 836 | this.fired = false; // Set fired flat to false 837 | this.player_.pause(); // Call end function 838 | this.player_.currentTime(this.timeEnd); 839 | this.suspendPlay(); 840 | } 841 | }; 842 | 843 | videojs.SelectionBar.prototype.process_loop = function() { 844 | var player = this.player; 845 | 846 | if (player && this.looping) { 847 | var current_time = player.currentTime(); 848 | 849 | if (current_time < this.timeStart || 850 | this.timeEnd > 0 && this.timeEnd < current_time) { 851 | player.currentTime(this.timeStart); 852 | } 853 | } 854 | }; 855 | 856 | /** 857 | * This is the left arrow to select the RangeSlider 858 | * @param {videojs.Player|Object} player 859 | * @param {Object=} options 860 | * @constructor 861 | */ 862 | videojs.SelectionBarLeft = videojs.extend(videojs.getComponent('Component'), { 863 | /** @constructor */ 864 | constructor : function(player, options) { 865 | Component.call(this, player, options); 866 | this.on('mousedown', this.onMouseDown); 867 | this.pressed = false; 868 | } 869 | }); 870 | 871 | videojs.SelectionBarLeft.prototype.init_ = function() { 872 | this.rs = this.player_.rangeslider; 873 | }; 874 | 875 | videojs.SelectionBarLeft.prototype.createEl = function() { 876 | return Component.prototype.createEl.call(this, 'div', { 877 | className : 'vjs-rangeslider-handle vjs-selectionbar-left-RS', 878 | innerHTML : 879 | '
' 880 | }); 881 | }; 882 | 883 | videojs.SelectionBarLeft.prototype.onMouseDown = function(event) { 884 | event.preventDefault(); 885 | blockTextSelection(); 886 | if (!this.rs.options.locked) { 887 | this.pressed = true; 888 | videojs.on(document, "mouseup", videojs.bind(this, this.onMouseUp)); 889 | addElClass(this.el_, 'active'); 890 | } 891 | }; 892 | 893 | videojs.SelectionBarLeft.prototype.onMouseUp = function(event) { 894 | videojs.off(document, "mouseup", this.onMouseUp, false); 895 | removeElClass(this.el_, 'active'); 896 | if (!this.rs.options.locked) { 897 | this.pressed = false; 898 | } 899 | }; 900 | 901 | /** 902 | * This is the right arrow to select the RangeSlider 903 | * @param {videojs.Player|Object} player 904 | * @param {Object=} options 905 | * @constructor 906 | */ 907 | videojs.SelectionBarRight = 908 | videojs.extend(videojs.getComponent('Component'), { 909 | /** @constructor */ 910 | constructor : function(player, options) { 911 | Component.call(this, player, options); 912 | this.on('mousedown', this.onMouseDown); 913 | this.pressed = false; 914 | } 915 | }); 916 | 917 | videojs.SelectionBarRight.prototype.init_ = function() { 918 | this.rs = this.player_.rangeslider; 919 | }; 920 | 921 | videojs.SelectionBarRight.prototype.createEl = function() { 922 | return Component.prototype.createEl.call(this, 'div', { 923 | className : 'vjs-rangeslider-handle vjs-selectionbar-right-RS', 924 | innerHTML : 925 | '
' 926 | }); 927 | }; 928 | 929 | videojs.SelectionBarRight.prototype.onMouseDown = function(event) { 930 | event.preventDefault(); 931 | blockTextSelection(); 932 | if (!this.rs.options.locked) { 933 | this.pressed = true; 934 | videojs.on(document, "mouseup", videojs.bind(this, this.onMouseUp)); 935 | addElClass(this.el_, 'active'); 936 | } 937 | }; 938 | 939 | videojs.SelectionBarRight.prototype.onMouseUp = function(event) { 940 | videojs.off(document, "mouseup", this.onMouseUp, false); 941 | removeElClass(this.el_, 'active'); 942 | if (!this.rs.options.locked) { 943 | this.pressed = false; 944 | } 945 | }; 946 | 947 | /** 948 | * This is the time panel 949 | * @param {videojs.Player|Object} player 950 | * @param {Object=} options 951 | * @constructor 952 | */ 953 | videojs.TimePanel = videojs.extend(videojs.getComponent('Component'), { 954 | /** @constructor */ 955 | constructor : function(player, options) { Component.call(this, player, options); } 956 | }); 957 | 958 | videojs.TimePanel.prototype.init_ = function() { 959 | this.rs = this.player_.rangeslider; 960 | }; 961 | 962 | videojs.TimePanel.prototype.options_ = { 963 | children : { 964 | 'TimePanelLeft' : {}, 965 | 'TimePanelRight' : {}, 966 | } 967 | }; 968 | 969 | videojs.TimePanel.prototype.createEl = function() { 970 | return Component.prototype.createEl.call(this, 'div', 971 | {className : 'vjs-timepanel-RS'}); 972 | }; 973 | 974 | /** 975 | * This is the left time panel 976 | * @param {videojs.Player|Object} player 977 | * @param {Object=} options 978 | * @constructor 979 | */ 980 | videojs.TimePanelLeft = videojs.extend(videojs.getComponent('Component'), { 981 | /** @constructor */ 982 | constructor : function(player, options) { Component.call(this, player, options); } 983 | }); 984 | 985 | videojs.TimePanelLeft.prototype.init_ = function() { 986 | this.rs = this.player_.rangeslider; 987 | }; 988 | 989 | videojs.TimePanelLeft.prototype.createEl = function() { 990 | return Component.prototype.createEl.call(this, 'div', { 991 | className : 'vjs-timepanel-left-RS', 992 | innerHTML : '00:00' 993 | }); 994 | }; 995 | 996 | /** 997 | * This is the right time panel 998 | * @param {videojs.Player|Object} player 999 | * @param {Object=} options 1000 | * @constructor 1001 | */ 1002 | videojs.TimePanelRight = videojs.extend(videojs.getComponent('Component'), { 1003 | /** @constructor */ 1004 | constructor : function(player, options) { Component.call(this, player, options); } 1005 | }); 1006 | 1007 | videojs.TimePanelRight.prototype.init_ = function() { 1008 | this.rs = this.player_.rangeslider; 1009 | }; 1010 | 1011 | videojs.TimePanelRight.prototype.createEl = function() { 1012 | return Component.prototype.createEl.call(this, 'div', { 1013 | className : 'vjs-timepanel-right-RS', 1014 | innerHTML : '00:00' 1015 | }); 1016 | }; 1017 | 1018 | /** 1019 | * This is the control time panel 1020 | * @param {videojs.Player|Object} player 1021 | * @param {Object=} options 1022 | * @constructor 1023 | */ 1024 | videojs.ControlTimePanel = videojs.extend(videojs.getComponent('Component'), { 1025 | /** @constructor */ 1026 | constructor : function(player, options) { Component.call(this, player, options); } 1027 | }); 1028 | 1029 | videojs.ControlTimePanel.prototype.init_ = function() { 1030 | this.rs = this.player_.rangeslider; 1031 | }; 1032 | 1033 | videojs.ControlTimePanel.prototype.options_ = { 1034 | children : { 1035 | 'ControlTimePanelLeft' : {}, 1036 | 'ControlTimePanelRight' : {}, 1037 | } 1038 | }; 1039 | 1040 | videojs.ControlTimePanel.prototype.createEl = function() { 1041 | return Component.prototype.createEl.call(this, 'div', { 1042 | className : 'vjs-controltimepanel-RS vjs-control', 1043 | }); 1044 | }; 1045 | 1046 | videojs.ControlTimePanel.prototype.enable = function(enable) { 1047 | var enable = typeof enable != 'undefined' ? enable : true; 1048 | this.rs.ctpl.el_.children[0].disabled = enable ? "" : "disabled"; 1049 | this.rs.ctpl.el_.children[1].disabled = enable ? "" : "disabled"; 1050 | this.rs.ctpl.el_.children[2].disabled = enable ? "" : "disabled"; 1051 | this.rs.ctpr.el_.children[0].disabled = enable ? "" : "disabled"; 1052 | this.rs.ctpr.el_.children[1].disabled = enable ? "" : "disabled"; 1053 | this.rs.ctpr.el_.children[2].disabled = enable ? "" : "disabled"; 1054 | }; 1055 | 1056 | /** 1057 | * This is the control left time panel 1058 | * @param {videojs.Player|Object} player 1059 | * @param {Object=} options 1060 | * @constructor 1061 | */ 1062 | videojs.ControlTimePanelLeft = 1063 | videojs.extend(videojs.getComponent('Component'), { 1064 | /** @constructor */ 1065 | constructor : function(player, options) { 1066 | Component.call(this, player, options); 1067 | this.on('keyup', this.onKeyUp); 1068 | this.on('keydown', this.onKeyDown); 1069 | } 1070 | }); 1071 | 1072 | videojs.ControlTimePanelLeft.prototype.init_ = function() { 1073 | this.rs = this.player_.rangeslider; 1074 | this.timeOld = {}; 1075 | }; 1076 | 1077 | videojs.ControlTimePanelLeft.prototype.createEl = function() { 1078 | return Component.prototype.createEl.call(this, 'div', { 1079 | className : 'vjs-controltimepanel-left-RS', 1080 | innerHTML : 1081 | 'Start: ::' 1082 | }); 1083 | }; 1084 | 1085 | videojs.ControlTimePanelLeft.prototype.onKeyDown = function(event) { 1086 | this.timeOld[0] = this.el_.children[0].value; 1087 | this.timeOld[1] = this.el_.children[1].value; 1088 | this.timeOld[2] = this.el_.children[2].value; 1089 | }; 1090 | 1091 | videojs.ControlTimePanelLeft.prototype.onKeyUp = function(event) { 1092 | this.rs._checkControlTime(0, this.el_.children, this.timeOld); 1093 | }; 1094 | 1095 | /** 1096 | * This is the control right time panel 1097 | * @param {videojs.Player|Object} player 1098 | * @param {Object=} options 1099 | * @constructor 1100 | */ 1101 | videojs.ControlTimePanelRight = 1102 | videojs.extend(videojs.getComponent('Component'), { 1103 | /** @constructor */ 1104 | constructor : function(player, options) { 1105 | Component.call(this, player, options); 1106 | this.on('keyup', this.onKeyUp); 1107 | this.on('keydown', this.onKeyDown); 1108 | } 1109 | }); 1110 | 1111 | videojs.ControlTimePanelRight.prototype.init_ = function() { 1112 | this.rs = this.player_.rangeslider; 1113 | this.timeOld = {}; 1114 | }; 1115 | 1116 | videojs.ControlTimePanelRight.prototype.createEl = function() { 1117 | return Component.prototype.createEl.call(this, 'div', { 1118 | className : 'vjs-controltimepanel-right-RS', 1119 | innerHTML : 1120 | 'End: ::' 1121 | }); 1122 | }; 1123 | 1124 | videojs.ControlTimePanelRight.prototype.onKeyDown = function(event) { 1125 | this.timeOld[0] = this.el_.children[0].value; 1126 | this.timeOld[1] = this.el_.children[1].value; 1127 | this.timeOld[2] = this.el_.children[2].value; 1128 | }; 1129 | 1130 | videojs.ControlTimePanelRight.prototype.onKeyUp = function(event) { 1131 | this.rs._checkControlTime(1, this.el_.children, this.timeOld); 1132 | }; 1133 | })(); 1134 | --------------------------------------------------------------------------------