├── .editorconfig ├── .gitignore ├── .jshintrc ├── .npmignore ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── bower.json ├── captions ├── captions.ar.vtt ├── captions.en.vtt ├── captions.ja.vtt ├── captions.ru.vtt └── captions.sv.vtt ├── css ├── videojs-transcript.css ├── videojs-transcript2.css └── videojs-transcript3.css ├── dist ├── videojs-transcript.js └── videojs-transcript.min.js ├── example.html ├── package.json ├── src ├── events.js ├── intro.js ├── main.js ├── options.js ├── outro.js ├── polyfill.js ├── scroller.js ├── tracklist.js ├── utils.js └── widget.js └── test ├── index.html ├── modules.html ├── modules.test.js └── videojs-transcript.test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules/ 4 | build/ 5 | *~ 6 | .sublime-* -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser" : true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "quotmark" : "single", 6 | "trailing" : true, 7 | "undef" : true, 8 | "predef" : [ 9 | "videojs" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | test/ 3 | *~ -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 7 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 8 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' + 9 | ' Licensed <%= pkg.license %> */\n', 10 | clean: { 11 | files: ['dist'] 12 | }, 13 | concat: { 14 | options: { 15 | banner: '<%= banner %>', 16 | stripBanners: true 17 | }, 18 | build: { 19 | src: ['src/intro.js', 20 | 'src/polyfill.js', 21 | 'src/options.js', 22 | 'src/utils.js', 23 | 'src/events.js', 24 | 'src/scroller.js', 25 | 'src/tracklist.js', 26 | 'src/widget.js', 27 | 'src/main.js', 28 | 'src/outro.js'], 29 | dest: 'dist/<%= pkg.name %>.js' 30 | } 31 | }, 32 | uglify: { 33 | options: { 34 | banner: '<%= banner %>' 35 | }, 36 | dist: { 37 | src: '<%= concat.build.dest %>', 38 | dest: 'dist/<%= pkg.name %>.min.js' 39 | } 40 | }, 41 | qunit: { 42 | files: 'test/**/*.html' 43 | }, 44 | jshint: { 45 | gruntfile: { 46 | options: { 47 | node: true 48 | }, 49 | src: 'Gruntfile.js' 50 | }, 51 | src: { 52 | options: { 53 | jshintrc: '.jshintrc' 54 | }, 55 | src: ['src/**/*.js', 56 | '!src/intro.js', 57 | '!src/outro.js'] 58 | }, 59 | test: { 60 | options: { 61 | jshintrc: '.jshintrc' 62 | }, 63 | src: ['test/**/*.js'] 64 | }, 65 | build: { 66 | options: { 67 | jshintrc: '.jshintrc' 68 | }, 69 | src: ['build/**/*.js'] 70 | } 71 | }, 72 | watch: { 73 | gruntfile: { 74 | files: '<%= jshint.gruntfile.src %>', 75 | tasks: ['jshint:gruntfile'] 76 | }, 77 | src: { 78 | files: '<%= jshint.src.src %>', 79 | tasks: ['jshint:src', 'qunit'] 80 | }, 81 | test: { 82 | files: '<%= jshint.test.src %>', 83 | tasks: ['jshint:test', 'qunit'] 84 | } 85 | } 86 | }); 87 | 88 | grunt.loadNpmTasks('grunt-contrib-clean'); 89 | grunt.loadNpmTasks('grunt-contrib-concat'); 90 | grunt.loadNpmTasks('grunt-contrib-uglify'); 91 | grunt.loadNpmTasks('grunt-contrib-qunit'); 92 | grunt.loadNpmTasks('grunt-contrib-jshint'); 93 | grunt.loadNpmTasks('grunt-contrib-watch'); 94 | 95 | grunt.registerTask('default', 96 | ['clean', 97 | //'jshint:src', 98 | 'concat', 99 | 'jshint:build', 100 | 'jshint:test', 101 | 'qunit', 102 | 'uglify']); 103 | }; 104 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Matthew Walsh, contributors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Video.js Transcript 2 | 3 | Creates interactive transcripts from text tracks. 4 | 5 | ## Alpha Release 3 6 | 7 | Please report any issues or feature requests on the tracker. Thank you! 8 | 9 | ## Getting Started 10 | 11 | Once you've added the plugin script to your page, you can use it with any video: 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 19 | 23 |
24 | 40 | 41 | ``` 42 | There's also a [working example](https://walsh9.github.io/videojs-transcript/example.html) of the plugin you can check out if you're having trouble. 43 | 44 | You'll also want to include one of the css files. 45 | You can style the plugin as you like but there are a few examples in the /css folder to get you started. 46 | 47 | ## Documentation 48 | ### Plugin Options 49 | 50 | You may pass in an options object to the plugin upon initialization. This 51 | object may contain any of the following properties: 52 | 53 | #### autoscroll 54 | **Default:** true 55 | 56 | Set to false to disable autoscrolling. 57 | 58 | #### scrollToCenter 59 | **Default:** false 60 | 61 | By default current row shows on the bottom on autoscrolling. Set to true to show it in the center 62 | 63 | #### clickArea 64 | **Default:** 'line' 65 | 66 | Set which elements in the transcript are clickable. 67 | Options are 'timestamp', 'text', the whole 'line', or 'none'. 68 | 69 | #### showTitle 70 | **Default:** true 71 | 72 | Show a title with the transcript widget. 73 | 74 | (Currently the title only says 'Transcript') 75 | 76 | #### showTrackSelector 77 | **Default:** true 78 | 79 | Show a track selector with the transcript widget. 80 | 81 | #### followPlayerTrack 82 | **Default:** true 83 | 84 | When you change the caption track on the video, the transcript changes tracks as well. 85 | 86 | #### stopScrollWhenInUse 87 | **Default:** true 88 | 89 | Don't autoscroll the transcript when the user is trying to scroll it. 90 | 91 | (This probably still has a few glitches to work out on touch screens and stuff right now) 92 | 93 | ### Plugin Methods 94 | **el()** 95 | 96 | Returns the DOM element containing the html transcript widget. You'll need to append this to your page. 97 | 98 | ## Release History 99 | 100 | ##### 0.8.0: Alpha Release 3 101 | 102 | * Updated for video.js 5.x 103 | 104 | ##### 0.7.2: Alpha Release 2 105 | 106 | * Updated for video.js 4.12 107 | 108 | ##### 0.7.1: Alpha Release 1 109 | 110 | * First release 111 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-transcript", 3 | "version": "0.8.1", 4 | "dependencies": { 5 | "video.js": "^5.6.0" 6 | }, 7 | "homepage": "https://github.com/walsh9/videojs-transcript", 8 | "authors": [ 9 | "walsh9" 10 | ], 11 | "description": "", 12 | "main": "", 13 | "license": "MIT", 14 | "ignore": [ 15 | "**/.*", 16 | "node_modules", 17 | "bower_components", 18 | "test", 19 | "tests" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /captions/captions.ar.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:15.042 --> 00:00:18.625 4 | ...إلى... إلى الشمال يمكن أن نرى 5 | ...يمكن أن نرى الـ 6 | 7 | 00:00:18.750 --> 00:00:20.958 8 | ...إلى اليمين يمكن أن نرى الـ 9 | 10 | 00:00:21.000 --> 00:00:23.125 11 | طاحنات الرؤوس... 12 | 13 | 00:00:23.208 --> 00:00:25.208 14 | كل شيئ آمن 15 | آمن كلية 16 | 17 | 00:00:26.333 --> 00:00:28.333 18 | إيمو ؟ 19 | 20 | 00:00:28.875 --> 00:00:30.958 21 | ! حذاري 22 | 23 | 00:00:47.125 --> 00:00:49.167 24 | هل أصبت ؟ 25 | 26 | 00:00:52.125 --> 00:00:54.833 27 | ...لا أظن ذلك 28 | وأنت ؟ 29 | 30 | 00:00:55.625 --> 00:00:57.625 31 | أنا بخير 32 | 33 | 00:00:57.667 --> 00:01:01.667 34 | ،قم يا إيمو 35 | المكان هنا غير آمن 36 | 37 | 00:01:02.208 --> 00:01:04.083 38 | لنذهب 39 | 40 | 00:01:04.167 --> 00:01:06.167 41 | وماذا بعد ؟ 42 | 43 | 00:01:06.167 --> 00:01:08.583 44 | ...سترى... سترى 45 | 46 | 00:01:16.167 --> 00:01:18.375 47 | إيمو، من هنا 48 | 49 | 00:01:34.958 --> 00:01:37.000 50 | ! إتبعني 51 | 52 | 00:02:11.125 --> 00:02:13.625 53 | ! أسرع يا إيمو 54 | 55 | 00:02:48.375 --> 00:02:50.375 56 | ! لست منتبها 57 | 58 | 00:02:50.750 --> 00:02:54.500 59 | ...أريد فقط أن أجيب الـ 60 | الهاتف... 61 | 62 | 00:02:55.000 --> 00:02:58.500 63 | ،إيمو، أنظر 64 | أقصد أنصت 65 | 66 | 00:02:59.750 --> 00:03:03.292 67 | عليك أن تتعلم الإصغاء 68 | 69 | 00:03:03.625 --> 00:03:05.917 70 | هذا ليس ضربا من اللهو 71 | 72 | 00:03:06.083 --> 00:03:09.958 73 | ...إنك 74 | أقصد إننا قد نموت بسهولة في هذا المكان 75 | 76 | 00:03:10.208 --> 00:03:14.125 77 | ...أنصت 78 | أنصت إلى أصوات الآلة 79 | 80 | 00:03:18.333 --> 00:03:20.417 81 | أنصت إلى نَفَسِك 82 | 83 | 00:04:27.208 --> 00:04:29.250 84 | ألا تمل أبدا من هذا ؟ 85 | 86 | 00:04:29.583 --> 00:04:31.583 87 | أمل ؟!؟ 88 | نعم - 89 | 90 | 00:04:31.750 --> 00:04:34.667 91 | إيمو؛ الآلة في دقتها... مثل الساعة 92 | 93 | 00:04:35.500 --> 00:04:37.708 94 | ...حركة ناشزة واحدة قد 95 | 96 | 00:04:37.833 --> 00:04:39.875 97 | تطرحك معجونا 98 | 99 | 00:04:41.042 --> 00:04:43.083 100 | ...أو ليست 101 | 102 | 00:04:43.125 --> 00:04:46.542 103 | ! عجينة يا إيمو 104 | أ هذا ما تريد ؟ أن تصبح عجينة ؟ 105 | 106 | 00:04:48.083 --> 00:04:50.083 107 | أيمو، أ هذا هدفك في الحياة ؟ 108 | 109 | 00:04:50.583 --> 00:04:52.667 110 | أن تصير عجينة ؟ 111 | 112 | 00:05:41.833 --> 00:05:43.875 113 | إيمو، أغمض عينيك 114 | 115 | 00:05:44.917 --> 00:05:47.000 116 | لماذا ؟ 117 | ! الآن - 118 | 119 | 00:05:53.750 --> 00:05:56.042 120 | حسن 121 | 122 | 00:05:59.542 --> 00:06:02.792 123 | ماذا ترى إلى شمالك يا إيمو ؟ 124 | 125 | 00:06:04.417 --> 00:06:06.500 126 | لا شيئ 127 | حقا ؟ - 128 | 129 | 00:06:06.542 --> 00:06:08.625 130 | لا، لا شيئ البتة 131 | 132 | 00:06:08.625 --> 00:06:12.417 133 | وماذا ترى إلى جهتك اليمنى يا إيمو ؟ 134 | 135 | 00:06:13.667 --> 00:06:17.833 136 | ،نفس الشيئ يا بروغ 137 | ! نفس الشيئ بالضبط؛ لا شيئ 138 | 139 | 00:06:17.875 --> 00:06:19.917 140 | عظيم 141 | 142 | 00:06:40.625 --> 00:06:42.958 143 | أنصت يا بروغ ! هل تسمع ذلك ؟ 144 | 145 | 00:06:43.625 --> 00:06:45.625 146 | هل نستطيع الذهاب إلى هناك ؟ 147 | 148 | 00:06:45.708 --> 00:06:47.792 149 | هناك ؟ 150 | نعم - 151 | 152 | 00:06:47.833 --> 00:06:49.833 153 | إنه غير آمن يا إيمو 154 | 155 | 00:06:49.917 --> 00:06:52.500 156 | صدقني، إنه غير آمن 157 | 158 | 00:06:53.292 --> 00:06:55.375 159 | ...لكن لعلي أستطيع 160 | 161 | 00:06:55.417 --> 00:06:57.417 162 | ...لكن 163 | ! لا - 164 | 165 | 00:06:57.667 --> 00:06:59.667 166 | ! لا 167 | 168 | 00:07:00.875 --> 00:07:03.750 169 | هل من أسئلة أخرى يا إيمو ؟ 170 | 171 | 00:07:04.250 --> 00:07:06.333 172 | لا 173 | 174 | 00:07:09.458 --> 00:07:11.542 175 | ...إيمو 176 | نعم - 177 | 178 | 00:07:11.875 --> 00:07:13.958 179 | ...لماذا يا إيمو... لماذا 180 | 181 | 00:07:15.292 --> 00:07:18.792 182 | لماذا لا تستطيع أن ترى حُسْن هذا المكان 183 | 184 | 00:07:18.833 --> 00:07:20.833 185 | ...والطريقة التي يعمل بها 186 | 187 | 00:07:20.875 --> 00:07:24.000 188 | وكيف... وكيف أنه غاية في الكمال 189 | 190 | 00:07:24.083 --> 00:07:27.417 191 | ! لا يا بروغ، لا أرى ذلك 192 | 193 | 00:07:27.542 --> 00:07:30.333 194 | لا أرى ذلك لأنه لا يوجد شيئ هناك 195 | 196 | 00:07:31.500 --> 00:07:35.333 197 | ثم لماذا يجب علي أن أسلم حياتي 198 | لشيئ لا وجود له ؟ 199 | 200 | 00:07:35.583 --> 00:07:37.625 201 | هل يمكنك أن تخبرني ؟ 202 | 203 | 00:07:37.708 --> 00:07:39.750 204 | ! أجبني 205 | 206 | 00:07:43.208 --> 00:07:47.333 207 | ...بروغ 208 | ! أنت معتوه يا هذا 209 | 210 | 00:07:47.375 --> 00:07:49.417 211 | ! إبعد عني 212 | 213 | 00:07:52.583 --> 00:07:55.083 214 | ! لا يا إيمو ! إنه فخ 215 | 216 | 00:07:55.833 --> 00:07:57.875 217 | ...إنه فخ 218 | 219 | 00:07:57.917 --> 00:08:01.750 220 | إلى جنبك الأيسر يمكنك أن ترى 221 | حدائق بابل المعلقة 222 | 223 | 00:08:02.250 --> 00:08:04.292 224 | هل تعجبك كفخ ؟ 225 | 226 | 00:08:05.458 --> 00:08:07.542 227 | لا يا أيمو 228 | 229 | 00:08:09.417 --> 00:08:12.792 230 | ...إلى جنبك الأيمن يمكنك رؤية 231 | حزر ماذا ؟ 232 | 233 | 00:08:13.000 --> 00:08:15.042 234 | ! عملاق رودس 235 | 236 | 00:08:15.125 --> 00:08:16.417 237 | ! لا 238 | 239 | 00:08:16.458 --> 00:08:20.500 240 | ،عملاق رودس 241 | وهو هنا خصيصا من أجلك يا بروغ 242 | 243 | 00:08:20.583 --> 00:08:22.583 244 | فقط من أجلك 245 | 246 | 00:08:51.333 --> 00:08:53.375 247 | إنه هناك 248 | 249 | 00:08:53.417 --> 00:08:55.500 250 | أنا أؤكد لك... إيمو 251 | 252 | 00:08:57.333 --> 00:09:00.000 253 | ...إنه 254 | -------------------------------------------------------------------------------- /captions/captions.en.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:15.000 --> 00:00:17.951 4 | At the left we can see... 5 | 6 | 00:00:18.166 --> 00:00:20.083 7 | At the right we can see the... 8 | 9 | 00:00:20.119 --> 00:00:21.962 10 | ...the head-snarlers 11 | 12 | 00:00:21.999 --> 00:00:24.368 13 | Everything is safe. 14 | Perfectly safe. 15 | 16 | 00:00:24.582 --> 00:00:27.035 17 | Emo? 18 | 19 | 00:00:28.206 --> 00:00:29.996 20 | Watch out! 21 | 22 | 00:00:47.037 --> 00:00:48.494 23 | Are you hurt? 24 | 25 | 00:00:51.994 --> 00:00:53.949 26 | I don't think so. 27 | You? 28 | 29 | 00:00:55.160 --> 00:00:56.985 30 | I'm Ok. 31 | 32 | 00:00:57.118 --> 00:01:01.111 33 | Get up. 34 | Emo, it's not safe here. 35 | 36 | 00:01:02.034 --> 00:01:03.573 37 | Let's go. 38 | 39 | 00:01:03.610 --> 00:01:05.114 40 | What's next? 41 | 42 | 00:01:05.200 --> 00:01:09.146 43 | You'll see! 44 | 45 | 00:01:16.032 --> 00:01:18.022 46 | Emo. 47 | This way. 48 | 49 | 00:01:34.237 --> 00:01:35.481 50 | Follow me! 51 | 52 | 00:02:11.106 --> 00:02:12.480 53 | Hurry Emo! 54 | 55 | 00:02:48.059 --> 00:02:49.930 56 | You're not paying attention! 57 | 58 | 00:02:50.142 --> 00:02:54.052 59 | I just want to answer the... 60 | ...phone. 61 | 62 | 00:02:54.974 --> 00:02:57.972 63 | Emo, look, 64 | I mean listen. 65 | 66 | 00:02:59.140 --> 00:03:02.008 67 | You have to learn to listen. 68 | 69 | 00:03:03.140 --> 00:03:04.965 70 | This is not some game. 71 | 72 | 00:03:05.056 --> 00:03:09.345 73 | You, I mean we, 74 | we could easily die out here. 75 | 76 | 00:03:10.014 --> 00:03:13.959 77 | Listen, 78 | listen to the sounds of the machine. 79 | 80 | 00:03:18.054 --> 00:03:20.009 81 | Listen to your breathing. 82 | 83 | 00:04:27.001 --> 00:04:28.956 84 | Well, don't you ever get tired of this? 85 | 86 | 00:04:29.084 --> 00:04:30.909 87 | Tired?!? 88 | 89 | 00:04:31.126 --> 00:04:34.491 90 | Emo, the machine is like clockwork. 91 | 92 | 00:04:35.083 --> 00:04:37.074 93 | One move out of place... 94 | 95 | 00:04:37.166 --> 00:04:39.121 96 | ...and you're ground to a pulp. 97 | 98 | 00:04:40.958 --> 00:04:42.004 99 | But isn't it - 100 | 101 | 00:04:42.041 --> 00:04:46.034 102 | Pulp, Emo! 103 | Is that what you want, pulp? 104 | 105 | 00:04:47.040 --> 00:04:48.995 106 | Emo, your goal in life... 107 | 108 | 00:04:50.081 --> 00:04:51.953 109 | ...pulp? 110 | 111 | 00:05:41.156 --> 00:05:43.028 112 | Emo, close your eyes. 113 | 114 | 00:05:44.156 --> 00:05:46.027 115 | Why? 116 | - Now! 117 | 118 | 00:05:51.155 --> 00:05:52.102 119 | Ok. 120 | 121 | 00:05:53.113 --> 00:05:54.688 122 | Good. 123 | 124 | 00:05:59.070 --> 00:06:02.103 125 | What do you see at your left side, Emo? 126 | 127 | 00:06:04.028 --> 00:06:05.899 128 | Nothing. 129 | - Really? 130 | 131 | 00:06:06.027 --> 00:06:07.105 132 | No, nothing at all. 133 | 134 | 00:06:07.944 --> 00:06:11.984 135 | And at your right, 136 | what do you see at your right side, Emo? 137 | 138 | 00:06:13.151 --> 00:06:16.102 139 | The same Proog, exactly the same... 140 | 141 | 00:06:16.942 --> 00:06:19.098 142 | ...nothing! 143 | - Great. 144 | 145 | 00:06:40.105 --> 00:06:42.724 146 | Listen Proog! Do you hear that! 147 | 148 | 00:06:43.105 --> 00:06:44.894 149 | Can we go here? 150 | 151 | 00:06:44.979 --> 00:06:47.894 152 | There? 153 | It isn't safe, Emo. 154 | 155 | 00:06:49.145 --> 00:06:52.013 156 | But... 157 | - Trust me, it's not. 158 | 159 | 00:06:53.020 --> 00:06:54.145 160 | Maybe I could... 161 | 162 | 00:06:54.181 --> 00:06:55.969 163 | No. 164 | 165 | 00:06:57.102 --> 00:06:59.934 166 | NO! 167 | 168 | 00:07:00.144 --> 00:07:03.058 169 | Any further questions, Emo? 170 | 171 | 00:07:03.976 --> 00:07:05.090 172 | No. 173 | 174 | 00:07:09.059 --> 00:07:10.089 175 | Emo? 176 | 177 | 00:07:11.142 --> 00:07:13.058 178 | Emo, why... 179 | 180 | 00:07:13.095 --> 00:07:14.022 181 | Emo... 182 | 183 | 00:07:14.058 --> 00:07:18.003 184 | ...why can't you see 185 | the beauty of this place? 186 | 187 | 00:07:18.141 --> 00:07:20.048 188 | The way it works. 189 | 190 | 00:07:20.140 --> 00:07:23.895 191 | How perfect it is. 192 | 193 | 00:07:23.932 --> 00:07:26.964 194 | No, Proog, I don't see. 195 | 196 | 00:07:27.056 --> 00:07:29.970 197 | I don't see because there's nothing there. 198 | 199 | 00:07:31.055 --> 00:07:34.965 200 | And why should I trust my 201 | life to something that isn't there? 202 | 203 | 00:07:35.055 --> 00:07:36.926 204 | Well can you tell me that? 205 | 206 | 00:07:37.054 --> 00:07:38.926 207 | Answer me! 208 | 209 | 00:07:42.970 --> 00:07:44.000 210 | Proog... 211 | 212 | 00:07:45.053 --> 00:07:46.985 213 | ...you're a sick man! 214 | 215 | 00:07:47.022 --> 00:07:48.918 216 | Stay away from me! 217 | 218 | 00:07:52.052 --> 00:07:54.884 219 | No! Emo! It's a trap! 220 | 221 | 00:07:55.135 --> 00:07:56.931 222 | Hah, it's a trap. 223 | 224 | 00:07:56.968 --> 00:08:01.043 225 | At the left side you can see 226 | the hanging gardens of Babylon! 227 | 228 | 00:08:01.967 --> 00:08:03.957 229 | How's that for a trap? 230 | 231 | 00:08:05.050 --> 00:08:06.922 232 | No, Emo. 233 | 234 | 00:08:09.008 --> 00:08:12.088 235 | At the right side you can see... 236 | ...well guess what... 237 | 238 | 00:08:12.924 --> 00:08:14.665 239 | ...the colossus of Rhodes! 240 | 241 | 00:08:15.132 --> 00:08:16.053 242 | No! 243 | 244 | 00:08:16.090 --> 00:08:21.919 245 | The colossus of Rhodes 246 | and it is here just for you Proog. 247 | 248 | 00:08:51.001 --> 00:08:52.923 249 | It is there... 250 | 251 | 00:08:52.959 --> 00:08:56.040 252 | I'm telling you, 253 | Emo... 254 | 255 | 00:08:57.000 --> 00:08:59.867 256 | ...it is. 257 | -------------------------------------------------------------------------------- /captions/captions.ja.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:15.042 --> 00:00:18.042 4 | 左に見えるのは… 5 | 6 | 00:00:18.750 --> 00:00:20.333 7 | 右に見えるのは… 8 | 9 | 00:00:20.417 --> 00:00:21.917 10 | …首刈り機 11 | 12 | 00:00:22.000 --> 00:00:24.625 13 | すべて安全 14 | 完璧に安全だ 15 | 16 | 00:00:26.333 --> 00:00:27.333 17 | イーモ? 18 | 19 | 00:00:28.875 --> 00:00:30.250 20 | 危ない! 21 | 22 | 00:00:47.125 --> 00:00:48.250 23 | ケガはないか? 24 | 25 | 00:00:51.917 --> 00:00:53.917 26 | ええ、多分… 27 | あなたは? 28 | 29 | 00:00:55.625 --> 00:00:57.125 30 | わしは平気だ 31 | 32 | 00:00:57.583 --> 00:01:01.667 33 | 起きてくれイーモ 34 | ここは危ない 35 | 36 | 00:01:02.208 --> 00:01:03.667 37 | 行こう 38 | 39 | 00:01:03.750 --> 00:01:04.917 40 | どこに? 41 | 42 | 00:01:05.875 --> 00:01:07.875 43 | すぐにわかるさ! 44 | 45 | 00:01:16.167 --> 00:01:18.375 46 | イーモ、こっちだ 47 | 48 | 00:01:34.958 --> 00:01:36.958 49 | ついて来るんだ! 50 | 51 | 00:02:11.583 --> 00:02:12.792 52 | イーモ、早く! 53 | 54 | 00:02:48.375 --> 00:02:50.083 55 | むやみにさわるな! 56 | 57 | 00:02:50.750 --> 00:02:54.500 58 | 僕はただ、電話に 59 | …出ようと 60 | 61 | 00:02:55.000 --> 00:02:58.208 62 | イーモ、見るんだ… 63 | いや、聞いてくれ 64 | 65 | 00:02:59.750 --> 00:03:02.292 66 | 君は「聞き方」を知る必要がある 67 | 68 | 00:03:03.625 --> 00:03:05.125 69 | これは遊びじゃない 70 | 71 | 00:03:06.167 --> 00:03:10.417 72 | 我々はここでは 73 | たやすく死ぬ 74 | 75 | 00:03:11.208 --> 00:03:14.125 76 | 機械の声を聞くんだ 77 | 78 | 00:03:18.333 --> 00:03:22.417 79 | 君の息づかいを聞くんだ 80 | 81 | 00:04:27.208 --> 00:04:29.250 82 | そんなことして疲れない? 83 | 84 | 00:04:29.583 --> 00:04:31.083 85 | 疲れる?! 86 | 87 | 00:04:31.750 --> 00:04:34.667 88 | この機械は非常に正確で 89 | 90 | 00:04:35.500 --> 00:04:37.708 91 | 一つ間違えば… 92 | 93 | 00:04:37.833 --> 00:04:40.792 94 | …地面に落ちてバラバラだ 95 | 96 | 00:04:41.042 --> 00:04:42.375 97 | え、でも― 98 | 99 | 00:04:42.417 --> 00:04:46.542 100 | バラバラだぞ、イーモ! 101 | それでいいのか? 102 | 103 | 00:04:48.083 --> 00:04:50.000 104 | バラバラで死ぬんだぞ? 105 | 106 | 00:04:50.583 --> 00:04:52.250 107 | バラバラだ! 108 | 109 | 00:05:41.833 --> 00:05:43.458 110 | イーモ、目を閉じるんだ 111 | 112 | 00:05:44.917 --> 00:05:46.583 113 | なぜ? 114 | ―早く! 115 | 116 | 00:05:53.750 --> 00:05:56.042 117 | それでいい 118 | 119 | 00:05:59.542 --> 00:06:03.792 120 | 左に見えるものは何だ、イーモ? 121 | 122 | 00:06:04.417 --> 00:06:06.000 123 | え…何も 124 | ―本当か? 125 | 126 | 00:06:06.333 --> 00:06:07.917 127 | 全く何も 128 | 129 | 00:06:08.042 --> 00:06:12.833 130 | では右は 131 | 何か見えるか、イーモ? 132 | 133 | 00:06:13.875 --> 00:06:16.917 134 | 同じだよプルーグ、全く同じ… 135 | 136 | 00:06:17.083 --> 00:06:18.583 137 | 何もない! 138 | 139 | 00:06:40.625 --> 00:06:43.208 140 | プルーグ!何か聞こえない? 141 | 142 | 00:06:43.625 --> 00:06:45.042 143 | あそこに行かないか? 144 | 145 | 00:06:45.208 --> 00:06:48.042 146 | あそこ? 147 | …安全じゃない 148 | 149 | 00:06:49.917 --> 00:06:52.500 150 | でも… 151 | ―本当に危ないぞ 152 | 153 | 00:06:53.292 --> 00:06:54.792 154 | 大丈夫だよ… 155 | 156 | 00:06:54.833 --> 00:06:56.333 157 | だめだ 158 | 159 | 00:06:57.667 --> 00:07:00.167 160 | だめだ! 161 | 162 | 00:07:00.875 --> 00:07:03.750 163 | まだ続ける気か、イーモ? 164 | 165 | 00:07:04.250 --> 00:07:05.917 166 | いいえ… 167 | 168 | 00:07:09.458 --> 00:07:10.833 169 | イーモ? 170 | 171 | 00:07:11.875 --> 00:07:13.542 172 | イーモ、なぜ… 173 | 174 | 00:07:13.583 --> 00:07:14.458 175 | イーモ… 176 | 177 | 00:07:14.500 --> 00:07:18.500 178 | …なぜここの美しさが 179 | 見えない? 180 | 181 | 00:07:18.833 --> 00:07:20.750 182 | 仕組みがこんなに… 183 | 184 | 00:07:20.875 --> 00:07:24.000 185 | こんなに完全なのに 186 | 187 | 00:07:24.083 --> 00:07:27.417 188 | もういいよ!プルーグ! 189 | 190 | 00:07:27.542 --> 00:07:30.333 191 | そこには何もないんだから 192 | 193 | 00:07:31.500 --> 00:07:35.333 194 | なぜ命を「ない」物に 195 | ゆだねなきゃ? 196 | 197 | 00:07:35.583 --> 00:07:37.125 198 | 教えてくれないか? 199 | 200 | 00:07:37.500 --> 00:07:39.167 201 | さあ! 202 | 203 | 00:07:43.208 --> 00:07:44.583 204 | プルーグ… 205 | 206 | 00:07:45.500 --> 00:07:47.333 207 | あなたは病気なんだ 208 | 209 | 00:07:47.375 --> 00:07:49.208 210 | 僕から離れてくれ 211 | 212 | 00:07:52.583 --> 00:07:55.083 213 | いかん!イーモ!ワナだ! 214 | 215 | 00:07:55.833 --> 00:07:57.167 216 | ワナだ? ふーん 217 | 218 | 00:07:57.208 --> 00:08:01.750 219 | 左に何が見える? 220 | バビロンの空中庭園! 221 | 222 | 00:08:02.250 --> 00:08:04.292 223 | これがワナとでも? 224 | 225 | 00:08:05.458 --> 00:08:07.125 226 | だめだ、イーモ 227 | 228 | 00:08:09.417 --> 00:08:12.792 229 | 右にあるのは… 230 | …すごい!… 231 | 232 | 00:08:13.000 --> 00:08:14.750 233 | …ロードス島の巨像だ! 234 | 235 | 00:08:15.833 --> 00:08:16.708 236 | やめろ! 237 | 238 | 00:08:16.750 --> 00:08:22.167 239 | この巨像はあなたの物 240 | プルーグ、あなたのだよ 241 | 242 | 00:08:51.333 --> 00:08:53.167 243 | いってるじゃないか… 244 | 245 | 00:08:53.208 --> 00:08:55.500 246 | そこにあるって、イーモ… 247 | 248 | 00:08:57.333 --> 00:09:00.000 249 | …あるって 250 | -------------------------------------------------------------------------------- /captions/captions.ru.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:14.958 --> 00:00:17.833 4 | Слева мы видим... 5 | 6 | 00:00:18.458 --> 00:00:20.208 7 | справа мы видим... 8 | 9 | 00:00:20.333 --> 00:00:21.875 10 | ...голово-клацов. 11 | 12 | 00:00:22.000 --> 00:00:24.583 13 | всё в порядке. 14 | в полном порядке. 15 | 16 | 00:00:26.333 --> 00:00:27.333 17 | Имо? 18 | 19 | 00:00:28.833 --> 00:00:30.250 20 | Осторожно! 21 | 22 | 00:00:47.125 --> 00:00:48.250 23 | Ты не ранен? 24 | 25 | 00:00:51.875 --> 00:00:53.875 26 | Вроде нет... 27 | а ты? 28 | 29 | 00:00:55.583 --> 00:00:57.125 30 | Я в порядке. 31 | 32 | 00:00:57.542 --> 00:01:01.625 33 | Вставай. 34 | Имо, здесь не безопасно. 35 | 36 | 00:01:02.208 --> 00:01:03.625 37 | Пойдём. 38 | 39 | 00:01:03.708 --> 00:01:05.708 40 | Что дальше? 41 | 42 | 00:01:05.833 --> 00:01:07.833 43 | Ты увидишь! 44 | 45 | 00:01:08.000 --> 00:01:08.833 46 | Ты увидишь... 47 | 48 | 00:01:16.167 --> 00:01:18.375 49 | Имо, сюда. 50 | 51 | 00:01:34.917 --> 00:01:35.750 52 | За мной! 53 | 54 | 00:02:11.542 --> 00:02:12.750 55 | Имо, быстрее! 56 | 57 | 00:02:48.375 --> 00:02:50.083 58 | Ты не обращаешь внимания! 59 | 60 | 00:02:50.708 --> 00:02:54.500 61 | Я только хотел ответить на ... 62 | ...звонок. 63 | 64 | 00:02:55.000 --> 00:02:58.208 65 | Имо, смотри, 66 | то есть слушай... 67 | 68 | 00:02:59.708 --> 00:03:02.292 69 | Ты должен учиться слушать. 70 | 71 | 00:03:03.250 --> 00:03:05.333 72 | Это не какая-нибудь игра. 73 | 74 | 00:03:06.000 --> 00:03:08.833 75 | Ты, вернее мы, легко можем погибнуть здесь. 76 | 77 | 00:03:10.000 --> 00:03:11.167 78 | Слушай... 79 | 80 | 00:03:11.667 --> 00:03:14.125 81 | слушай звуки машины. 82 | 83 | 00:03:18.333 --> 00:03:20.417 84 | Слушай своё дыхание. 85 | 86 | 00:04:27.208 --> 00:04:29.250 87 | И не надоест тебе это? 88 | 89 | 00:04:29.542 --> 00:04:31.083 90 | Надоест?!? 91 | 92 | 00:04:31.708 --> 00:04:34.625 93 | Имо! Машина - 94 | она как часовой механизм. 95 | 96 | 00:04:35.500 --> 00:04:37.667 97 | Одно движение не туда... 98 | 99 | 00:04:37.792 --> 00:04:39.750 100 | ...и тебя размелют в месиво! 101 | 102 | 00:04:41.042 --> 00:04:42.375 103 | А разве это не - 104 | 105 | 00:04:42.417 --> 00:04:46.500 106 | Месиво, Имо! 107 | ты этого хочешь? месиво? 108 | 109 | 00:04:48.083 --> 00:04:50.000 110 | Имо, твоя цель в жизни? 111 | 112 | 00:04:50.542 --> 00:04:52.250 113 | Месиво! 114 | 115 | 00:05:41.792 --> 00:05:43.458 116 | Имо, закрой глаза. 117 | 118 | 00:05:44.875 --> 00:05:46.542 119 | Зачем? 120 | - Ну же! 121 | 122 | 00:05:51.500 --> 00:05:52.333 123 | Ладно. 124 | 125 | 00:05:53.708 --> 00:05:56.042 126 | Хорошо. 127 | 128 | 00:05:59.500 --> 00:06:02.750 129 | Что ты видишь слева от себя, Имо? 130 | 131 | 00:06:04.417 --> 00:06:06.000 132 | Ничего. 133 | - Точно? 134 | 135 | 00:06:06.333 --> 00:06:07.875 136 | да, совсем ничего. 137 | 138 | 00:06:08.042 --> 00:06:12.708 139 | А справа от себя, 140 | что ты видишь справа от себя, Имо? 141 | 142 | 00:06:13.833 --> 00:06:16.875 143 | Да то же Пруг, в точности то же... 144 | 145 | 00:06:17.042 --> 00:06:18.500 146 | Ничего! 147 | 148 | 00:06:18.667 --> 00:06:19.500 149 | Прекрасно... 150 | 151 | 00:06:40.583 --> 00:06:42.917 152 | Прислушайся, Пруг! Ты слышишь это? 153 | 154 | 00:06:43.583 --> 00:06:45.042 155 | Может, мы пойдём туда? 156 | 157 | 00:06:45.208 --> 00:06:48.042 158 | Туда? 159 | Это не безопасно, Имо. 160 | 161 | 00:06:49.875 --> 00:06:52.500 162 | Но... 163 | - Поверь мне, это так. 164 | 165 | 00:06:53.292 --> 00:06:54.750 166 | Может я бы ... 167 | 168 | 00:06:54.792 --> 00:06:56.333 169 | Нет. 170 | 171 | 00:06:57.625 --> 00:06:59.583 172 | - Но... 173 | - НЕТ! 174 | 175 | 00:06:59.708 --> 00:07:00.833 176 | Нет! 177 | 178 | 00:07:00.833 --> 00:07:03.708 179 | Ещё вопросы, Имо? 180 | 181 | 00:07:04.250 --> 00:07:05.875 182 | Нет. 183 | 184 | 00:07:09.458 --> 00:07:10.792 185 | Имо? 186 | 187 | 00:07:11.833 --> 00:07:13.500 188 | Имо, почему... 189 | 190 | 00:07:13.542 --> 00:07:14.458 191 | Имо... 192 | 193 | 00:07:14.500 --> 00:07:18.500 194 | ...почему? почему ты не видишь 195 | красоты этого места? 196 | 197 | 00:07:18.792 --> 00:07:20.708 198 | То как оно работает. 199 | 200 | 00:07:20.833 --> 00:07:24.000 201 | Как совершенно оно. 202 | 203 | 00:07:24.083 --> 00:07:27.417 204 | Нет, Пруг, я не вижу. 205 | 206 | 00:07:27.500 --> 00:07:30.333 207 | Я не вижу, потому что здесь ничего нет. 208 | 209 | 00:07:31.375 --> 00:07:35.333 210 | И почему я должен доверять свою жизнь 211 | чему-то, чего здесь нет? 212 | 213 | 00:07:35.542 --> 00:07:37.125 214 | это ты мне можешь сказать? 215 | 216 | 00:07:37.500 --> 00:07:39.167 217 | Ответь мне! 218 | 219 | 00:07:43.208 --> 00:07:44.542 220 | Пруг... 221 | 222 | 00:07:45.500 --> 00:07:47.333 223 | Ты просто больной! 224 | 225 | 00:07:47.375 --> 00:07:48.500 226 | Отстань от меня. 227 | 228 | 00:07:48.625 --> 00:07:49.917 229 | Имо... 230 | 231 | 00:07:52.542 --> 00:07:55.083 232 | Нет! Имо! Это ловушка! 233 | 234 | 00:07:55.792 --> 00:07:57.167 235 | Это ловушка! 236 | 237 | 00:07:57.208 --> 00:08:01.708 238 | Слева от себя вы можете увидеть 239 | Висящие сады Семирамиды! 240 | 241 | 00:08:02.250 --> 00:08:04.292 242 | Сойдёт за ловушку? 243 | 244 | 00:08:05.458 --> 00:08:07.125 245 | Нет, Имо. 246 | 247 | 00:08:09.417 --> 00:08:12.750 248 | Справа от себя вы можете увидеть... 249 | ...угадай кого... 250 | 251 | 00:08:13.000 --> 00:08:14.708 252 | ...Колосса Родосского! 253 | 254 | 00:08:15.500 --> 00:08:16.625 255 | Нет! 256 | 257 | 00:08:16.667 --> 00:08:21.125 258 | Колосс Родосский! 259 | И он здесь специально для тебя, Пруг. 260 | 261 | 00:08:21.167 --> 00:08:22.208 262 | Специально для тебя... 263 | 264 | 00:08:51.333 --> 00:08:53.167 265 | Она здесь есть! 266 | 267 | 00:08:53.208 --> 00:08:55.500 268 | Говорю тебе, 269 | Имо... 270 | 271 | 00:08:57.333 --> 00:09:00.000 272 | ...она есть... есть... 273 | -------------------------------------------------------------------------------- /captions/captions.sv.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00:15.042 --> 00:00:18.250 4 | Till vänster kan vi se... 5 | Ser vi... 6 | 7 | 00:00:18.708 --> 00:00:20.333 8 | Till höger ser vi... 9 | 10 | 00:00:20.417 --> 00:00:21.958 11 | ...huvudkaparna. 12 | 13 | 00:00:22.000 --> 00:00:24.792 14 | Allt är säkert, 15 | alldeles ofarligt. 16 | 17 | 00:00:24.917 --> 00:00:26.833 18 | Emo? 19 | 20 | 00:00:28.750 --> 00:00:30.167 21 | Se upp! 22 | 23 | 00:00:46.708 --> 00:00:48.750 24 | Är du skadad? 25 | 26 | 00:00:51.875 --> 00:00:54.458 27 | Jag tror inte det... 28 | Är du? 29 | 30 | 00:00:55.292 --> 00:00:57.333 31 | Jag är ok. 32 | 33 | 00:00:57.542 --> 00:01:01.625 34 | Res dig upp Emo. 35 | Det är inte säkert här. 36 | 37 | 00:01:02.208 --> 00:01:03.625 38 | Kom så går vi. 39 | 40 | 00:01:03.708 --> 00:01:05.708 41 | Vad nu då? 42 | 43 | 00:01:05.833 --> 00:01:07.833 44 | Du får se... 45 | 46 | 00:01:08.042 --> 00:01:10.417 47 | Du får se. 48 | 49 | 00:01:15.958 --> 00:01:18.375 50 | Emo, den här vägen. 51 | 52 | 00:01:34.417 --> 00:01:36.750 53 | Följ efter mig! 54 | 55 | 00:02:11.250 --> 00:02:13.250 56 | Skynda dig, Emo! 57 | 58 | 00:02:48.375 --> 00:02:50.583 59 | Du är inte uppmärksam! 60 | 61 | 00:02:50.708 --> 00:02:54.500 62 | Jag vill bara svara... 63 | ... i telefonen. 64 | 65 | 00:02:54.500 --> 00:02:58.208 66 | Emo, se här... 67 | Lyssna menar jag. 68 | 69 | 00:02:59.708 --> 00:03:02.292 70 | Du måste lära dig att lyssna. 71 | 72 | 00:03:03.292 --> 00:03:05.208 73 | Det här är ingen lek. 74 | 75 | 00:03:05.250 --> 00:03:08.917 76 | Du... Jag menar vi, 77 | vi skulle kunna dö här ute. 78 | 79 | 00:03:09.917 --> 00:03:11.417 80 | Lyssna... 81 | 82 | 00:03:11.708 --> 00:03:14.833 83 | Lyssna på ljuden från maskinen. 84 | 85 | 00:03:18.125 --> 00:03:21.417 86 | Lyssna på dina andetag. 87 | 88 | 00:04:26.625 --> 00:04:29.250 89 | Tröttnar du aldrig på det här? 90 | 91 | 00:04:29.542 --> 00:04:31.083 92 | Tröttnar!? 93 | 94 | 00:04:31.208 --> 00:04:33.458 95 | Emo, maskinen är som... 96 | 97 | 00:04:33.458 --> 00:04:35.333 98 | Som ett urverk. 99 | 100 | 00:04:35.417 --> 00:04:37.167 101 | Ett felsteg... 102 | 103 | 00:04:37.208 --> 00:04:39.750 104 | ...och du blir krossad. 105 | 106 | 00:04:41.042 --> 00:04:42.292 107 | Men är det inte - 108 | 109 | 00:04:42.292 --> 00:04:47.000 110 | Krossad, Emo! 111 | Är det vad du vill bli? Krossad till mos? 112 | 113 | 00:04:47.500 --> 00:04:50.542 114 | Emo, är det ditt mål i livet? 115 | 116 | 00:04:50.667 --> 00:04:53.250 117 | Att bli mos!? 118 | 119 | 00:05:41.375 --> 00:05:43.458 120 | Emo, blunda. 121 | 122 | 00:05:44.375 --> 00:05:46.542 123 | Varför då? 124 | - Blunda! 125 | 126 | 00:05:51.292 --> 00:05:55.042 127 | Ok. 128 | - Bra. 129 | 130 | 00:05:59.500 --> 00:06:02.750 131 | Vad ser du till vänster om dig Emo? 132 | 133 | 00:06:04.125 --> 00:06:06.292 134 | Ingenting. 135 | - Säker? 136 | 137 | 00:06:06.333 --> 00:06:07.958 138 | Ingenting alls. 139 | 140 | 00:06:08.042 --> 00:06:12.625 141 | Jaså, och till höger om dig... 142 | Vad ser du där, Emo? 143 | 144 | 00:06:13.750 --> 00:06:15.583 145 | Samma där Proog... 146 | 147 | 00:06:15.583 --> 00:06:18.083 148 | Exakt samma där, ingenting! 149 | 150 | 00:06:18.083 --> 00:06:19.667 151 | Perfekt. 152 | 153 | 00:06:40.500 --> 00:06:42.917 154 | Lyssna Proog! Hör du? 155 | 156 | 00:06:43.500 --> 00:06:45.125 157 | Kan vi gå dit? 158 | 159 | 00:06:45.208 --> 00:06:48.125 160 | Gå dit? 161 | Det är inte tryggt. 162 | 163 | 00:06:49.583 --> 00:06:52.583 164 | Men, men... 165 | - Tro mig, det inte säkert. 166 | 167 | 00:06:53.000 --> 00:06:54.292 168 | Men kanske om jag - 169 | 170 | 00:06:54.292 --> 00:06:56.333 171 | Nej. 172 | 173 | 00:06:57.208 --> 00:07:00.167 174 | Men - 175 | - Nej, NEJ! 176 | 177 | 00:07:00.917 --> 00:07:03.792 178 | Några fler frågor Emo? 179 | 180 | 00:07:04.250 --> 00:07:05.875 181 | Nej. 182 | 183 | 00:07:09.542 --> 00:07:11.375 184 | Emo? 185 | - Ja? 186 | 187 | 00:07:11.542 --> 00:07:15.667 188 | Emo, varför... 189 | 190 | 00:07:15.792 --> 00:07:18.583 191 | Varför kan du inte se skönheten i det här? 192 | 193 | 00:07:18.792 --> 00:07:21.708 194 | Hur det fungerar. 195 | 196 | 00:07:21.833 --> 00:07:24.000 197 | Hur perfekt det är. 198 | 199 | 00:07:24.083 --> 00:07:27.333 200 | Nej Proog, jag kan inte se det. 201 | 202 | 00:07:27.333 --> 00:07:30.333 203 | Jag ser det inte, för det finns inget där. 204 | 205 | 00:07:31.292 --> 00:07:35.333 206 | Och varför skulle jag lägga mitt liv 207 | i händerna på något som inte finns? 208 | 209 | 00:07:35.333 --> 00:07:37.083 210 | Kan du berätta det för mig? 211 | - Emo... 212 | 213 | 00:07:37.083 --> 00:07:39.167 214 | Svara mig! 215 | 216 | 00:07:43.500 --> 00:07:45.208 217 | Proog... 218 | 219 | 00:07:45.208 --> 00:07:47.083 220 | Du är inte frisk! 221 | 222 | 00:07:47.167 --> 00:07:49.292 223 | Håll dig borta från mig! 224 | 225 | 00:07:52.292 --> 00:07:55.083 226 | Nej! Emo! 227 | Det är en fälla! 228 | 229 | 00:07:55.375 --> 00:07:57.208 230 | Heh, det är en fälla. 231 | 232 | 00:07:57.208 --> 00:08:01.708 233 | På vänster sida ser vi... 234 | Babylons hängande trädgårdar! 235 | 236 | 00:08:01.958 --> 00:08:04.000 237 | Vad sägs om den fällan? 238 | 239 | 00:08:05.458 --> 00:08:07.333 240 | Nej, Emo. 241 | 242 | 00:08:08.917 --> 00:08:12.667 243 | Till höger ser vi... 244 | Gissa! 245 | 246 | 00:08:12.750 --> 00:08:15.125 247 | Rhodos koloss! 248 | 249 | 00:08:15.375 --> 00:08:16.500 250 | Nej! 251 | 252 | 00:08:16.500 --> 00:08:20.250 253 | Kolossen på Rhodos! 254 | Och den är här för din skull, Proog... 255 | 256 | 00:08:20.250 --> 00:08:23.250 257 | Bara för din skull. 258 | 259 | 00:08:50.917 --> 00:08:53.250 260 | Den är där... 261 | 262 | 00:08:53.625 --> 00:08:56.417 263 | Tro mig. 264 | Emo... 265 | 266 | 00:08:57.000 --> 00:09:00.000 267 | Det är den. 268 | Det är den... 269 | -------------------------------------------------------------------------------- /css/videojs-transcript.css: -------------------------------------------------------------------------------- 1 | #transcript { 2 | width: 600px; 3 | margin: auto; 4 | font-family: Arial, sans-serif; 5 | } 6 | 7 | .transcript-body { 8 | height: 200px; 9 | overflow-y: scroll; 10 | } 11 | 12 | .transcript-line { 13 | position: relative; 14 | padding: 5px; 15 | cursor: pointer; 16 | line-height: 1.3; 17 | } 18 | 19 | .transcript-timestamp { 20 | position: absolute; 21 | display: inline-block; 22 | color: #333; 23 | width: 50px; 24 | } 25 | 26 | .transcript-text { 27 | display: block; 28 | margin-left: 50px; 29 | } 30 | 31 | .transcript-line:hover { 32 | background-color: #c9f3f3; 33 | } 34 | 35 | .transcript-line.is-active { 36 | background-color: #e2fbfb; 37 | } 38 | -------------------------------------------------------------------------------- /css/videojs-transcript2.css: -------------------------------------------------------------------------------- 1 | #transcript { 2 | width: 600px; 3 | margin: auto; 4 | font-family: Arial, sans-serif; 5 | } 6 | 7 | .transcript-line { 8 | position: relative; 9 | padding: 5px; 10 | cursor: pointer; 11 | line-height: 1.3; 12 | } 13 | 14 | .transcript-timestamp { 15 | position: absolute; 16 | display: inline-block; 17 | color: #666; 18 | width: 50px; 19 | } 20 | 21 | .transcript-text { 22 | display: block; 23 | margin-left: 50px; 24 | padding-left: 10px; 25 | } 26 | 27 | .transcript-line:hover .transcript-text { 28 | border-left: 3px solid #c9f3f3; 29 | padding-left: 7px; 30 | } 31 | 32 | .transcript-line.is-active .transcript-text { 33 | border-left: 3px solid #ccc; 34 | padding-left: 7px; 35 | font-weight: bold; 36 | } 37 | 38 | .transcript-line.is-active .transcript-timestamp { 39 | color: #111; 40 | } -------------------------------------------------------------------------------- /css/videojs-transcript3.css: -------------------------------------------------------------------------------- 1 | .video-container { 2 | margin: 40px auto; 3 | position: relative; 4 | width: 900px; 5 | } 6 | #video { 7 | margin: 0; 8 | position: absolute; 9 | } 10 | #transcript { 11 | position: absolute; 12 | left: 600px; 13 | width: 298px; 14 | font-family: Arial, sans-serif; 15 | overflow-x: scroll; 16 | height: 298px; 17 | border: 1px solid #111; 18 | } 19 | .transcript-header { 20 | height: 19px; 21 | padding: 2px; 22 | font-weight: bold; 23 | } 24 | .transcript-selector { 25 | height: 25px; 26 | } 27 | .transcript-body { 28 | width: 600px; 29 | overflow-y: scroll; 30 | background-color: #e7e7e7; 31 | height: 250px; 32 | } 33 | 34 | .transcript-line { 35 | position: relative; 36 | padding: 5px; 37 | cursor: pointer; 38 | line-height: 1.3; 39 | } 40 | 41 | .transcript-line:nth-child(odd) { 42 | background-color: #f5f5f5; 43 | } 44 | 45 | 46 | .transcript-timestamp { 47 | position: absolute; 48 | display: inline-block; 49 | color: #333; 50 | width: 50px; 51 | } 52 | 53 | .transcript-text { 54 | display: block; 55 | margin-left: 50px; 56 | } 57 | 58 | .transcript-line:hover, 59 | .transcript-line:hover .transcript-timestamp, 60 | .transcript-line:hover .transcript-text { 61 | background-color: #777; 62 | color: #e7e7e7; 63 | } 64 | 65 | .transcript-line.is-active, 66 | .transcript-line.is-active .transcript-timestamp, 67 | .transcript-line.is-active .transcript-text { 68 | background-color: #555; 69 | color: #e7e7e7; 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /dist/videojs-transcript.js: -------------------------------------------------------------------------------- 1 | /*! videojs-transcript - v0.8.1 - 2017-04-21 2 | * Copyright (c) 2017 Matthew Walsh; Licensed MIT */ 3 | (function (window, videojs) { 4 | 'use strict'; 5 | 6 | 7 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 8 | // MIT license 9 | // https://gist.github.com/paulirish/1579671 10 | (function() { 11 | var lastTime = 0; 12 | var vendors = ['ms', 'moz', 'webkit', 'o']; 13 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 14 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 15 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 16 | || window[vendors[x]+'CancelRequestAnimationFrame']; 17 | } 18 | if (!window.requestAnimationFrame) 19 | window.requestAnimationFrame = function(callback, element) { 20 | var currTime = new Date().getTime(); 21 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 22 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 23 | timeToCall); 24 | lastTime = currTime + timeToCall; 25 | return id; 26 | }; 27 | if (!window.cancelAnimationFrame) 28 | window.cancelAnimationFrame = function(id) { 29 | clearTimeout(id); 30 | }; 31 | }()); 32 | 33 | // Object.create() polyfill 34 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill 35 | if (typeof Object.create != 'function') { 36 | Object.create = (function() { 37 | var Object = function() {}; 38 | return function (prototype) { 39 | if (arguments.length > 1) { 40 | throw Error('Second argument not supported'); 41 | } 42 | if (typeof prototype != 'object') { 43 | throw TypeError('Argument must be an object'); 44 | } 45 | Object.prototype = prototype; 46 | var result = new Object(); 47 | Object.prototype = null; 48 | return result; 49 | }; 50 | })(); 51 | } 52 | 53 | // forEach polyfill 54 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill 55 | if (!Array.prototype.forEach) { 56 | Array.prototype.forEach = function(callback, thisArg) { 57 | var T, k; 58 | if (this == null) { 59 | throw new TypeError(' this is null or not defined'); 60 | } 61 | var O = Object(this); 62 | var len = O.length >>> 0; 63 | if (typeof callback != "function") { 64 | throw new TypeError(callback + ' is not a function'); 65 | } 66 | if (arguments.length > 1) { 67 | T = thisArg; 68 | } 69 | k = 0; 70 | while (k < len) { 71 | var kValue; 72 | if (k in O) { 73 | kValue = O[k]; 74 | callback.call(T, kValue, k, O); 75 | } 76 | k++; 77 | } 78 | }; 79 | } 80 | 81 | // classList polyfill 82 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 83 | ;if("document" in self&&!("classList" in document.createElement("_"))){(function(j){"use strict";if(!("Element" in j)){return}var a="classList",f="prototype",m=j.Element[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p 0 && min < 10) ? '0' + min : min; 113 | if (hour > 0) { 114 | return hour + ':' + min + ':' + sec; 115 | } 116 | return min + ':' + sec; 117 | }, 118 | localize: function (string) { 119 | return string; // TODO: do something here; 120 | }, 121 | createEl: function (elementName, classSuffix) { 122 | classSuffix = classSuffix || ''; 123 | var el = document.createElement(elementName); 124 | el.className = plugin.prefix + classSuffix; 125 | return el; 126 | }, 127 | extend: function(obj) { 128 | var type = typeof obj; 129 | if (!(type === 'function' || type === 'object' && !!obj)) { 130 | return obj; 131 | } 132 | var source, prop; 133 | for (var i = 1, length = arguments.length; i < length; i++) { 134 | source = arguments[i]; 135 | for (prop in source) { 136 | obj[prop] = source[prop]; 137 | } 138 | } 139 | return obj; 140 | } 141 | }; 142 | }(my)); 143 | 144 | var eventEmitter = { 145 | handlers_: [], 146 | on: function on (object, eventtype, callback) { 147 | if (typeof callback === 'function') { 148 | this.handlers_.push([object, eventtype, callback]); 149 | } else { 150 | throw new TypeError('Callback is not a function.'); 151 | } 152 | }, 153 | trigger: function trigger (object, eventtype) { 154 | this.handlers_.forEach( function(h) { 155 | if (h[0] === object && 156 | h[1] === eventtype) { 157 | h[2].apply(); 158 | } 159 | }); 160 | } 161 | }; 162 | 163 | var scrollerProto = function(plugin) { 164 | 165 | var initHandlers = function (el) { 166 | var self = this; 167 | // The scroll event. We want to keep track of when the user is scrolling the transcript. 168 | el.addEventListener('scroll', function () { 169 | if (self.isAutoScrolling) { 170 | 171 | // If isAutoScrolling was set to true, we can set it to false and then ignore this event. 172 | // It wasn't the user. 173 | self.isAutoScrolling = false; // event handled 174 | } else { 175 | 176 | // We only care about when the user scrolls. Set userIsScrolling to true and add a nice class. 177 | self.userIsScrolling = true; 178 | el.classList.add('is-inuse'); 179 | } 180 | }); 181 | 182 | // The mouseover event. 183 | el.addEventListener('mouseenter', function () { 184 | self.mouseIsOverTranscript = true; 185 | }); 186 | el.addEventListener('mouseleave', function () { 187 | self.mouseIsOverTranscript = false; 188 | 189 | // Have a small delay before deciding user as done interacting. 190 | setTimeout(function () { 191 | 192 | // Make sure the user didn't move the pointer back in. 193 | if (!self.mouseIsOverTranscript) { 194 | self.userIsScrolling = false; 195 | el.classList.remove('is-inuse'); 196 | } 197 | }, 1000); 198 | }); 199 | }; 200 | 201 | // Init instance variables 202 | var init = function (element, plugin) { 203 | this.element = element; 204 | this.userIsScrolling = false; 205 | 206 | //default to true in case user isn't using a mouse; 207 | this.mouseIsOverTranscript = true; 208 | this.isAutoScrolling = true; 209 | initHandlers.call(this, this.element); 210 | return this; 211 | }; 212 | 213 | // Easing function for smoothness. 214 | var easeOut = function (time, start, change, duration) { 215 | return start + change * Math.sin(Math.min(1, time / duration) * (Math.PI / 2)); 216 | }; 217 | 218 | // Animate the scrolling. 219 | var scrollTo = function (element, newPos, duration) { 220 | var startTime = Date.now(); 221 | var startPos = element.scrollTop; 222 | var self = this; 223 | 224 | // Don't try to scroll beyond the limits. You won't get there and this will loop forever. 225 | newPos = Math.max(0, newPos); 226 | newPos = Math.min(element.scrollHeight - element.clientHeight, newPos); 227 | var change = newPos - startPos; 228 | 229 | // This inner function is called until the elements scrollTop reaches newPos. 230 | var updateScroll = function () { 231 | var now = Date.now(); 232 | var time = now - startTime; 233 | self.isAutoScrolling = true; 234 | element.scrollTop = easeOut(time, startPos, change, duration); 235 | if (element.scrollTop !== newPos) { 236 | requestAnimationFrame(updateScroll, element); 237 | } 238 | }; 239 | requestAnimationFrame(updateScroll, element); 240 | }; 241 | 242 | // Scroll an element's parent so the element is brought into view. 243 | var scrollToElement = function (element) { 244 | if (this.canScroll()) { 245 | var parent = element.parentElement; 246 | var parentOffsetBottom = parent.offsetTop + parent.clientHeight; 247 | var elementOffsetBottom = element.offsetTop + element.clientHeight; 248 | var relTop = element.offsetTop - parent.offsetTop; 249 | var relBottom = (element.offsetTop + element.clientHeight) - parent.offsetTop; 250 | var centerPosCorrection = 0; 251 | var newPos; 252 | 253 | if (plugin.settings.scrollToCenter){ 254 | centerPosCorrection = Math.round(parent.clientHeight/2 - element.clientHeight/2); 255 | } 256 | // If the top of the line is above the top of the parent view, were scrolling up, 257 | // so we want to move the top of the element downwards to match the top of the parent. 258 | if (relTop < parent.scrollTop + centerPosCorrection) { 259 | newPos = element.offsetTop - parent.offsetTop -centerPosCorrection; 260 | 261 | // If the bottom of the line is below the parent view, we're scrolling down, so we want the 262 | // bottom edge of the line to move up to meet the bottom edge of the parent. 263 | } else if (relBottom > (parent.scrollTop + parent.clientHeight) - centerPosCorrection) { 264 | newPos = elementOffsetBottom - parentOffsetBottom + centerPosCorrection; 265 | } 266 | 267 | // Don't try to scroll if we haven't set a new position. If we didn't 268 | // set a new position the line is already in view (i.e. It's not above 269 | // or below the view) 270 | // And don't try to scroll when the element is already in position. 271 | if (newPos !== undefined && parent.scrollTop !== newPos) { 272 | scrollTo.call(this, parent, newPos, 400); 273 | } 274 | } 275 | }; 276 | 277 | // Return whether the element is scrollable. 278 | var canScroll = function () { 279 | var el = this.element; 280 | return el.scrollHeight > el.offsetHeight; 281 | }; 282 | 283 | // Return whether the user is interacting with the transcript. 284 | var inUse = function () { 285 | return this.userIsScrolling; 286 | }; 287 | 288 | return { 289 | init: init, 290 | to : scrollToElement, 291 | canScroll : canScroll, 292 | inUse : inUse 293 | } 294 | }(my); 295 | 296 | var scroller = function(element) { 297 | return Object.create(scrollerProto).init(element); 298 | }; 299 | 300 | 301 | /*global my*/ 302 | var trackList = function (plugin) { 303 | var activeTrack; 304 | return { 305 | get: function () { 306 | var validTracks = []; 307 | var i, track; 308 | my.tracks = my.player.textTracks(); 309 | for (i = 0; i < my.tracks.length; i++) { 310 | track = my.tracks[i]; 311 | if (track.kind === 'captions' || track.kind === 'subtitles') { 312 | validTracks.push(track); 313 | } 314 | } 315 | return validTracks; 316 | }, 317 | active: function (tracks) { 318 | var i, track; 319 | for (i = 0; i < my.tracks.length; i++) { 320 | track = my.tracks[i]; 321 | if (track.mode === 'showing') { 322 | activeTrack = track; 323 | return track; 324 | } 325 | } 326 | // fallback to first track 327 | return activeTrack || tracks[0]; 328 | }, 329 | }; 330 | }(my); 331 | 332 | /*globals utils, eventEmitter, my, scrollable*/ 333 | 334 | var widget = function (plugin) { 335 | var my = {}; 336 | my.element = {}; 337 | my.body = {}; 338 | var on = function (event, callback) { 339 | eventEmitter.on(this, event, callback); 340 | }; 341 | var trigger = function (event) { 342 | eventEmitter.trigger(this, event); 343 | }; 344 | var createTitle = function () { 345 | var header = utils.createEl('header', '-header'); 346 | header.textContent = utils.localize('Transcript'); 347 | return header; 348 | }; 349 | var createSelector = function (){ 350 | var selector = utils.createEl('select', '-selector'); 351 | plugin.validTracks.forEach(function (track, i) { 352 | var option = document.createElement('option'); 353 | option.value = i; 354 | option.textContent = track.label + ' (' + track.language + ')'; 355 | selector.appendChild(option); 356 | }); 357 | selector.addEventListener('change', function (e) { 358 | setTrack(document.querySelector('#' + plugin.prefix + '-' + plugin.player.id() + ' option:checked').value); 359 | trigger('trackchanged'); 360 | }); 361 | return selector; 362 | }; 363 | var clickToSeekHandler = function (event) { 364 | var clickedClasses = event.target.classList; 365 | var clickedTime = event.target.getAttribute('data-begin') || event.target.parentElement.getAttribute('data-begin'); 366 | if (clickedTime !== undefined && clickedTime !== null) { // can be zero 367 | if ((plugin.settings.clickArea === 'line') || // clickArea: 'line' activates on all elements 368 | (plugin.settings.clickArea === 'timestamp' && clickedClasses.contains(plugin.prefix + '-timestamp')) || 369 | (plugin.settings.clickArea === 'text' && clickedClasses.contains(plugin.prefix + '-text'))) { 370 | plugin.player.currentTime(clickedTime); 371 | } 372 | } 373 | }; 374 | var createLine = function (cue) { 375 | var line = utils.createEl('div', '-line'); 376 | var timestamp = utils.createEl('span', '-timestamp'); 377 | var text = utils.createEl('span', '-text'); 378 | line.setAttribute('data-begin', cue.startTime); 379 | line.setAttribute('tabindex', my._options.tabIndex || 0); 380 | timestamp.textContent = utils.secondsToTime(cue.startTime); 381 | text.innerHTML = cue.text; 382 | line.appendChild(timestamp); 383 | line.appendChild(text); 384 | return line; 385 | }; 386 | var createTranscriptBody = function (track) { 387 | if (typeof track !== 'object') { 388 | track = plugin.player.textTracks()[track]; 389 | } 390 | var body = utils.createEl('div', '-body'); 391 | var line, i; 392 | var fragment = document.createDocumentFragment(); 393 | // activeCues returns null when the track isn't loaded (for now?) 394 | if (!track.activeCues) { 395 | // If cues aren't loaded, set mode to hidden, wait, and try again. 396 | // But don't hide an active track. In that case, just wait and try again. 397 | if (track.mode !== 'showing') { 398 | track.mode = 'hidden'; 399 | } 400 | window.setTimeout(function() { 401 | createTranscriptBody(track); 402 | }, 100); 403 | } else { 404 | var cues = track.cues; 405 | for (i = 0; i < cues.length; i++) { 406 | line = createLine(cues[i]); 407 | fragment.appendChild(line); 408 | } 409 | body.innerHTML = ''; 410 | body.appendChild(fragment); 411 | body.setAttribute('lang', track.language); 412 | body.scroll = scroller(body); 413 | body.addEventListener('click', clickToSeekHandler); 414 | my.element.replaceChild(body, my.body); 415 | my.body = body; 416 | } 417 | 418 | }; 419 | var create = function (options) { 420 | var el = document.createElement('div'); 421 | my._options = options; 422 | my.element = el; 423 | el.setAttribute('id', plugin.prefix + '-' + plugin.player.id()); 424 | if (plugin.settings.showTitle) { 425 | var title = createTitle(); 426 | el.appendChild(title); 427 | } 428 | if (plugin.settings.showTrackSelector) { 429 | var selector = createSelector(); 430 | el.appendChild(selector); 431 | } 432 | my.body = utils.createEl('div', '-body'); 433 | el.appendChild(my.body); 434 | setTrack(plugin.currentTrack); 435 | return this; 436 | }; 437 | var setTrack = function (track, trackCreated) { 438 | createTranscriptBody(track, trackCreated); 439 | }; 440 | var setCue = function (time) { 441 | var active, i, line, begin, end; 442 | var lines = my.body.children; 443 | for (i = 0; i < lines.length; i++) { 444 | line = lines[i]; 445 | begin = line.getAttribute('data-begin'); 446 | if (i < lines.length - 1) { 447 | end = lines[i + 1].getAttribute('data-begin'); 448 | } else { 449 | end = plugin.player.duration() || Infinity; 450 | } 451 | if (time > begin && time < end) { 452 | if (!line.classList.contains('is-active')) { // don't update if it hasn't changed 453 | line.classList.add('is-active'); 454 | if (plugin.settings.autoscroll && !(plugin.settings.stopScrollWhenInUse && my.body.scroll.inUse())) { 455 | my.body.scroll.to(line); 456 | } 457 | } 458 | } else { 459 | line.classList.remove('is-active'); 460 | } 461 | } 462 | }; 463 | var el = function () { 464 | return my.element; 465 | }; 466 | return { 467 | create: create, 468 | setTrack: setTrack, 469 | setCue: setCue, 470 | el : el, 471 | on: on, 472 | trigger: trigger, 473 | }; 474 | 475 | }(my); 476 | 477 | var transcript = function (options) { 478 | my.player = this; 479 | my.validTracks = trackList.get(); 480 | my.currentTrack = trackList.active(my.validTracks); 481 | my.settings = videojs.mergeOptions(defaults, options); 482 | my.widget = widget.create(options); 483 | var timeUpdate = function () { 484 | my.widget.setCue(my.player.currentTime()); 485 | }; 486 | var updateTrack = function () { 487 | my.currentTrack = trackList.active(my.validTracks); 488 | my.widget.setTrack(my.currentTrack); 489 | }; 490 | if (my.validTracks.length > 0) { 491 | updateTrack(); 492 | my.player.on('timeupdate', timeUpdate); 493 | if (my.settings.followPlayerTrack) { 494 | my.player.on('captionstrackchange', updateTrack); 495 | my.player.on('subtitlestrackchange', updateTrack); 496 | } 497 | } else { 498 | throw new Error('videojs-transcript: No tracks found!'); 499 | } 500 | return { 501 | el: function () { 502 | return my.widget.el(); 503 | }, 504 | setTrack: my.widget.setTrack 505 | }; 506 | }; 507 | videojs.plugin('transcript', transcript); 508 | 509 | }(window, videojs)); 510 | -------------------------------------------------------------------------------- /dist/videojs-transcript.min.js: -------------------------------------------------------------------------------- 1 | /*! videojs-transcript - v0.8.1 - 2017-04-21 2 | * Copyright (c) 2017 Matthew Walsh; Licensed MIT */ 3 | !function(a,b){"use strict";!function(){for(var b=0,c=["ms","moz","webkit","o"],d=0;d1)throw Error("Second argument not supported");if("object"!=typeof b)throw TypeError("Argument must be an object");a.prototype=b;var c=new a;return a.prototype=null,c}}()),Array.prototype.forEach||(Array.prototype.forEach=function(a,b){var c,d;if(null==this)throw new TypeError(" this is null or not defined");var e=Object(this),f=e.length>>>0;if("function"!=typeof a)throw new TypeError(a+" is not a function");for(arguments.length>1&&(c=b),d=0;f>d;){var g;d in e&&(g=e[d],a.call(c,g,d,e)),d++}}),"document"in self&&!("classList"in document.createElement("_"))&&!function(a){if("Element"in a){var b="classList",c="prototype",d=a.Element[c],e=Object,f=String[c].trim||function(){return this.replace(/^\s+|\s+$/g,"")},g=Array[c].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1},h=function(a,b){this.name=a,this.code=DOMException[a],this.message=b},i=function(a,b){if(""===b)throw new h("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(b))throw new h("INVALID_CHARACTER_ERR","String contains an invalid character");return g.call(a,b)},j=function(a){for(var b=f.call(a.getAttribute("class")||""),c=b?b.split(/\s+/):[],d=0,e=c.length;e>d;d++)this.push(c[d]);this._updateClassName=function(){a.setAttribute("class",this.toString())}},k=j[c]=[],l=function(){return new j(this)};if(h[c]=Error[c],k.item=function(a){return this[a]||null},k.contains=function(a){return a+="",-1!==i(this,a)},k.add=function(){var a,b=arguments,c=0,d=b.length,e=!1;do a=b[c]+"",-1===i(this,a)&&(this.push(a),e=!0);while(++cd?"0"+d:d,c=b>0&&10>c?"0"+c:c,b>0?b+":"+c+":"+d:c+":"+d},localize:function(a){return a},createEl:function(b,c){c=c||"";var d=document.createElement(b);return d.className=a.prefix+c,d},extend:function(a){var b=typeof a;if(!("function"===b||"object"===b&&a))return a;for(var c,d,e=1,f=arguments.length;f>e;e++){c=arguments[e];for(d in c)a[d]=c[d]}return a}}}(c),f={handlers_:[],on:function(a,b,c){if("function"!=typeof c)throw new TypeError("Callback is not a function.");this.handlers_.push([a,b,c])},trigger:function(a,b){this.handlers_.forEach(function(c){c[0]===a&&c[1]===b&&c[2].apply()})}},g=function(a){var b=function(a){var b=this;a.addEventListener("scroll",function(){b.isAutoScrolling?b.isAutoScrolling=!1:(b.userIsScrolling=!0,a.classList.add("is-inuse"))}),a.addEventListener("mouseenter",function(){b.mouseIsOverTranscript=!0}),a.addEventListener("mouseleave",function(){b.mouseIsOverTranscript=!1,setTimeout(function(){b.mouseIsOverTranscript||(b.userIsScrolling=!1,a.classList.remove("is-inuse"))},1e3)})},c=function(a,c){return this.element=a,this.userIsScrolling=!1,this.mouseIsOverTranscript=!0,this.isAutoScrolling=!0,b.call(this,this.element),this},d=function(a,b,c,d){return b+c*Math.sin(Math.min(1,a/d)*(Math.PI/2))},e=function(a,b,c){var e=Date.now(),f=a.scrollTop,g=this;b=Math.max(0,b),b=Math.min(a.scrollHeight-a.clientHeight,b);var h=b-f,i=function(){var j=Date.now(),k=j-e;g.isAutoScrolling=!0,a.scrollTop=d(k,f,h,c),a.scrollTop!==b&&requestAnimationFrame(i,a)};requestAnimationFrame(i,a)},f=function(b){if(this.canScroll()){var c,d=b.parentElement,f=d.offsetTop+d.clientHeight,g=b.offsetTop+b.clientHeight,h=b.offsetTop-d.offsetTop,i=b.offsetTop+b.clientHeight-d.offsetTop,j=0;a.settings.scrollToCenter&&(j=Math.round(d.clientHeight/2-b.clientHeight/2)),hd.scrollTop+d.clientHeight-j&&(c=g-f+j),void 0!==c&&d.scrollTop!==c&&e.call(this,d,c,400)}},g=function(){var a=this.element;return a.scrollHeight>a.offsetHeight},h=function(){return this.userIsScrolling};return{init:c,to:f,canScroll:g,inUse:h}}(c),h=function(a){return Object.create(g).init(a)},i=function(a){var b;return{get:function(){var a,b,d=[];for(c.tracks=c.player.textTracks(),a=0;af&&g>a?e.classList.contains("is-active")||(e.classList.add("is-active"),!b.settings.autoscroll||b.settings.stopScrollWhenInUse&&c.body.scroll.inUse()||c.body.scroll.to(e)):e.classList.remove("is-active")},q=function(){return c.element};return{create:n,setTrack:o,setCue:p,el:q,on:d,trigger:g}}(c),k=function(a){c.player=this,c.validTracks=i.get(),c.currentTrack=i.active(c.validTracks),c.settings=b.mergeOptions(d,a),c.widget=j.create(a);var e=function(){c.widget.setCue(c.player.currentTime())},f=function(){c.currentTrack=i.active(c.validTracks),c.widget.setTrack(c.currentTrack)};if(!(c.validTracks.length>0))throw new Error("videojs-transcript: No tracks found!");return f(),c.player.on("timeupdate",e),c.settings.followPlayerTrack&&(c.player.on("captionstrackchange",f),c.player.on("subtitlestrackchange",f)),{el:function(){return c.widget.el()},setTrack:c.widget.setTrack}};b.plugin("transcript",k)}(window,videojs); -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js Transcript 6 | 7 | 8 | 9 | 10 | 11 | 26 | 27 | 28 | 29 | 30 |
31 |

32 | You can see the Video.js Transcript plugin in action below. 33 | Look at the source of this page to see how to use it with your videos. 34 |

35 |
36 |
37 | 53 |
54 |
55 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-transcript", 3 | "version": "0.8.1", 4 | "author": "Matthew Walsh", 5 | "description": "Creates interactive transcripts from text tracks.", 6 | "license": "MIT", 7 | "keywords": [ 8 | "videojs", 9 | "html5", 10 | "flash", 11 | "video", 12 | "player" 13 | ], 14 | "dependencies": {}, 15 | "devDependencies": { 16 | "grunt-contrib-clean": "^0.4", 17 | "grunt-contrib-concat": "^0.3", 18 | "grunt-contrib-jshint": "^0.6", 19 | "grunt-contrib-qunit": "^0.2", 20 | "grunt-contrib-uglify": "^0.2", 21 | "grunt-contrib-watch": "^0.4", 22 | "qunitjs": "^1.12" 23 | }, 24 | "peerDependencies": { 25 | "video.js": "^5.6.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Basic event handling. 3 | */ 4 | 5 | var eventEmitter = { 6 | handlers_: [], 7 | on: function on (object, eventtype, callback) { 8 | if (typeof callback === 'function') { 9 | this.handlers_.push([object, eventtype, callback]); 10 | } else { 11 | throw new TypeError('Callback is not a function.'); 12 | } 13 | }, 14 | trigger: function trigger (object, eventtype) { 15 | this.handlers_.forEach( function(h) { 16 | if (h[0] === object && 17 | h[1] === eventtype) { 18 | h[2].apply(); 19 | } 20 | }); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/intro.js: -------------------------------------------------------------------------------- 1 | (function (window, videojs) { 2 | 'use strict'; 3 | 4 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /*global window, videojs, my, defaults, trackList, widget*/ 2 | var transcript = function (options) { 3 | my.player = this; 4 | my.validTracks = trackList.get(); 5 | my.currentTrack = trackList.active(my.validTracks); 6 | my.settings = videojs.mergeOptions(defaults, options); 7 | my.widget = widget.create(options); 8 | var timeUpdate = function () { 9 | my.widget.setCue(my.player.currentTime()); 10 | }; 11 | var updateTrack = function () { 12 | my.currentTrack = trackList.active(my.validTracks); 13 | my.widget.setTrack(my.currentTrack); 14 | }; 15 | if (my.validTracks.length > 0) { 16 | updateTrack(); 17 | my.player.on('timeupdate', timeUpdate); 18 | if (my.settings.followPlayerTrack) { 19 | my.player.on('captionstrackchange', updateTrack); 20 | my.player.on('subtitlestrackchange', updateTrack); 21 | } 22 | } else { 23 | throw new Error('videojs-transcript: No tracks found!'); 24 | } 25 | return { 26 | el: function () { 27 | return my.widget.el(); 28 | }, 29 | setTrack: my.widget.setTrack 30 | }; 31 | }; 32 | videojs.plugin('transcript', transcript); 33 | -------------------------------------------------------------------------------- /src/options.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Shared Setup 3 | */ 4 | 5 | // Global settings 6 | var my = {}; 7 | my.settings = {}; 8 | my.prefix = 'transcript'; 9 | my.player = this; 10 | 11 | // Defaults 12 | var defaults = { 13 | autoscroll: true, 14 | clickArea: 'text', 15 | showTitle: true, 16 | showTrackSelector: true, 17 | followPlayerTrack: true, 18 | scrollToCenter: false, 19 | stopScrollWhenInUse: true, 20 | }; 21 | -------------------------------------------------------------------------------- /src/outro.js: -------------------------------------------------------------------------------- 1 | }(window, videojs)); 2 | -------------------------------------------------------------------------------- /src/polyfill.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Polyfills 3 | */ 4 | 5 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 6 | // MIT license 7 | // https://gist.github.com/paulirish/1579671 8 | (function() { 9 | var lastTime = 0; 10 | var vendors = ['ms', 'moz', 'webkit', 'o']; 11 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 12 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 13 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 14 | || window[vendors[x]+'CancelRequestAnimationFrame']; 15 | } 16 | if (!window.requestAnimationFrame) 17 | window.requestAnimationFrame = function(callback, element) { 18 | var currTime = new Date().getTime(); 19 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 20 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 21 | timeToCall); 22 | lastTime = currTime + timeToCall; 23 | return id; 24 | }; 25 | if (!window.cancelAnimationFrame) 26 | window.cancelAnimationFrame = function(id) { 27 | clearTimeout(id); 28 | }; 29 | }()); 30 | 31 | // Object.create() polyfill 32 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create#Polyfill 33 | if (typeof Object.create != 'function') { 34 | Object.create = (function() { 35 | var Object = function() {}; 36 | return function (prototype) { 37 | if (arguments.length > 1) { 38 | throw Error('Second argument not supported'); 39 | } 40 | if (typeof prototype != 'object') { 41 | throw TypeError('Argument must be an object'); 42 | } 43 | Object.prototype = prototype; 44 | var result = new Object(); 45 | Object.prototype = null; 46 | return result; 47 | }; 48 | })(); 49 | } 50 | 51 | // forEach polyfill 52 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach#Polyfill 53 | if (!Array.prototype.forEach) { 54 | Array.prototype.forEach = function(callback, thisArg) { 55 | var T, k; 56 | if (this == null) { 57 | throw new TypeError(' this is null or not defined'); 58 | } 59 | var O = Object(this); 60 | var len = O.length >>> 0; 61 | if (typeof callback != "function") { 62 | throw new TypeError(callback + ' is not a function'); 63 | } 64 | if (arguments.length > 1) { 65 | T = thisArg; 66 | } 67 | k = 0; 68 | while (k < len) { 69 | var kValue; 70 | if (k in O) { 71 | kValue = O[k]; 72 | callback.call(T, kValue, k, O); 73 | } 74 | k++; 75 | } 76 | }; 77 | } 78 | 79 | // classList polyfill 80 | /*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/ 81 | ;if("document" in self&&!("classList" in document.createElement("_"))){(function(j){"use strict";if(!("Element" in j)){return}var a="classList",f="prototype",m=j.Element[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p (parent.scrollTop + parent.clientHeight) - centerPosCorrection) { 105 | newPos = elementOffsetBottom - parentOffsetBottom + centerPosCorrection; 106 | } 107 | 108 | // Don't try to scroll if we haven't set a new position. If we didn't 109 | // set a new position the line is already in view (i.e. It's not above 110 | // or below the view) 111 | // And don't try to scroll when the element is already in position. 112 | if (newPos !== undefined && parent.scrollTop !== newPos) { 113 | scrollTo.call(this, parent, newPos, 400); 114 | } 115 | } 116 | }; 117 | 118 | // Return whether the element is scrollable. 119 | var canScroll = function () { 120 | var el = this.element; 121 | return el.scrollHeight > el.offsetHeight; 122 | }; 123 | 124 | // Return whether the user is interacting with the transcript. 125 | var inUse = function () { 126 | return this.userIsScrolling; 127 | }; 128 | 129 | return { 130 | init: init, 131 | to : scrollToElement, 132 | canScroll : canScroll, 133 | inUse : inUse 134 | } 135 | }(my); 136 | 137 | var scroller = function(element) { 138 | return Object.create(scrollerProto).init(element); 139 | }; 140 | 141 | -------------------------------------------------------------------------------- /src/tracklist.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Tracklist Helper 3 | */ 4 | 5 | /*global my*/ 6 | var trackList = function (plugin) { 7 | var activeTrack; 8 | return { 9 | get: function () { 10 | var validTracks = []; 11 | var i, track; 12 | my.tracks = my.player.textTracks(); 13 | for (i = 0; i < my.tracks.length; i++) { 14 | track = my.tracks[i]; 15 | if (track.kind === 'captions' || track.kind === 'subtitles') { 16 | validTracks.push(track); 17 | } 18 | } 19 | return validTracks; 20 | }, 21 | active: function (tracks) { 22 | var i, track; 23 | for (i = 0; i < my.tracks.length; i++) { 24 | track = my.tracks[i]; 25 | if (track.mode === 'showing') { 26 | activeTrack = track; 27 | return track; 28 | } 29 | } 30 | // fallback to first track 31 | return activeTrack || tracks[0]; 32 | }, 33 | }; 34 | }(my); 35 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Utils 3 | */ 4 | 5 | /*global my*/ 6 | var utils = (function (plugin) { 7 | return { 8 | secondsToTime: function (timeInSeconds) { 9 | var hour = Math.floor(timeInSeconds / 3600); 10 | var min = Math.floor(timeInSeconds % 3600 / 60); 11 | var sec = Math.floor(timeInSeconds % 60); 12 | sec = (sec < 10) ? '0' + sec : sec; 13 | min = (hour > 0 && min < 10) ? '0' + min : min; 14 | if (hour > 0) { 15 | return hour + ':' + min + ':' + sec; 16 | } 17 | return min + ':' + sec; 18 | }, 19 | localize: function (string) { 20 | return string; // TODO: do something here; 21 | }, 22 | createEl: function (elementName, classSuffix) { 23 | classSuffix = classSuffix || ''; 24 | var el = document.createElement(elementName); 25 | el.className = plugin.prefix + classSuffix; 26 | return el; 27 | }, 28 | extend: function(obj) { 29 | var type = typeof obj; 30 | if (!(type === 'function' || type === 'object' && !!obj)) { 31 | return obj; 32 | } 33 | var source, prop; 34 | for (var i = 1, length = arguments.length; i < length; i++) { 35 | source = arguments[i]; 36 | for (prop in source) { 37 | obj[prop] = source[prop]; 38 | } 39 | } 40 | return obj; 41 | } 42 | }; 43 | }(my)); 44 | -------------------------------------------------------------------------------- /src/widget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Create and Manipulate DOM Widgets 3 | */ 4 | 5 | /*globals utils, eventEmitter, my, scrollable*/ 6 | 7 | var widget = function (plugin) { 8 | var my = {}; 9 | my.element = {}; 10 | my.body = {}; 11 | var on = function (event, callback) { 12 | eventEmitter.on(this, event, callback); 13 | }; 14 | var trigger = function (event) { 15 | eventEmitter.trigger(this, event); 16 | }; 17 | var createTitle = function () { 18 | var header = utils.createEl('header', '-header'); 19 | header.textContent = utils.localize('Transcript'); 20 | return header; 21 | }; 22 | var createSelector = function (){ 23 | var selector = utils.createEl('select', '-selector'); 24 | plugin.validTracks.forEach(function (track, i) { 25 | var option = document.createElement('option'); 26 | option.value = i; 27 | option.textContent = track.label + ' (' + track.language + ')'; 28 | selector.appendChild(option); 29 | }); 30 | selector.addEventListener('change', function (e) { 31 | setTrack(document.querySelector('#' + plugin.prefix + '-' + plugin.player.id() + ' option:checked').value); 32 | trigger('trackchanged'); 33 | }); 34 | return selector; 35 | }; 36 | var clickToSeekHandler = function (event) { 37 | var clickedClasses = event.target.classList; 38 | var clickedTime = event.target.getAttribute('data-begin') || event.target.parentElement.getAttribute('data-begin'); 39 | if (clickedTime !== undefined && clickedTime !== null) { // can be zero 40 | if ((plugin.settings.clickArea === 'line') || // clickArea: 'line' activates on all elements 41 | (plugin.settings.clickArea === 'timestamp' && clickedClasses.contains(plugin.prefix + '-timestamp')) || 42 | (plugin.settings.clickArea === 'text' && clickedClasses.contains(plugin.prefix + '-text'))) { 43 | plugin.player.currentTime(clickedTime); 44 | } 45 | } 46 | }; 47 | var createLine = function (cue) { 48 | var line = utils.createEl('div', '-line'); 49 | var timestamp = utils.createEl('span', '-timestamp'); 50 | var text = utils.createEl('span', '-text'); 51 | line.setAttribute('data-begin', cue.startTime); 52 | line.setAttribute('tabindex', my._options.tabIndex || 0); 53 | timestamp.textContent = utils.secondsToTime(cue.startTime); 54 | text.innerHTML = cue.text; 55 | line.appendChild(timestamp); 56 | line.appendChild(text); 57 | return line; 58 | }; 59 | var createTranscriptBody = function (track) { 60 | if (typeof track !== 'object') { 61 | track = plugin.player.textTracks()[track]; 62 | } 63 | var body = utils.createEl('div', '-body'); 64 | var line, i; 65 | var fragment = document.createDocumentFragment(); 66 | // activeCues returns null when the track isn't loaded (for now?) 67 | if (!track.activeCues) { 68 | // If cues aren't loaded, set mode to hidden, wait, and try again. 69 | // But don't hide an active track. In that case, just wait and try again. 70 | if (track.mode !== 'showing') { 71 | track.mode = 'hidden'; 72 | } 73 | window.setTimeout(function() { 74 | createTranscriptBody(track); 75 | }, 100); 76 | } else { 77 | var cues = track.cues; 78 | for (i = 0; i < cues.length; i++) { 79 | line = createLine(cues[i]); 80 | fragment.appendChild(line); 81 | } 82 | body.innerHTML = ''; 83 | body.appendChild(fragment); 84 | body.setAttribute('lang', track.language); 85 | body.scroll = scroller(body); 86 | body.addEventListener('click', clickToSeekHandler); 87 | my.element.replaceChild(body, my.body); 88 | my.body = body; 89 | } 90 | 91 | }; 92 | var create = function (options) { 93 | var el = document.createElement('div'); 94 | my._options = options; 95 | my.element = el; 96 | el.setAttribute('id', plugin.prefix + '-' + plugin.player.id()); 97 | if (plugin.settings.showTitle) { 98 | var title = createTitle(); 99 | el.appendChild(title); 100 | } 101 | if (plugin.settings.showTrackSelector) { 102 | var selector = createSelector(); 103 | el.appendChild(selector); 104 | } 105 | my.body = utils.createEl('div', '-body'); 106 | el.appendChild(my.body); 107 | setTrack(plugin.currentTrack); 108 | return this; 109 | }; 110 | var setTrack = function (track, trackCreated) { 111 | createTranscriptBody(track, trackCreated); 112 | }; 113 | var setCue = function (time) { 114 | var active, i, line, begin, end; 115 | var lines = my.body.children; 116 | for (i = 0; i < lines.length; i++) { 117 | line = lines[i]; 118 | begin = line.getAttribute('data-begin'); 119 | if (i < lines.length - 1) { 120 | end = lines[i + 1].getAttribute('data-begin'); 121 | } else { 122 | end = plugin.player.duration() || Infinity; 123 | } 124 | if (time > begin && time < end) { 125 | if (!line.classList.contains('is-active')) { // don't update if it hasn't changed 126 | line.classList.add('is-active'); 127 | if (plugin.settings.autoscroll && !(plugin.settings.stopScrollWhenInUse && my.body.scroll.inUse())) { 128 | my.body.scroll.to(line); 129 | } 130 | } 131 | } else { 132 | line.classList.remove('is-active'); 133 | } 134 | } 135 | }; 136 | var el = function () { 137 | return my.element; 138 | }; 139 | return { 140 | create: create, 141 | setTrack: setTrack, 142 | setCue: setCue, 143 | el : el, 144 | on: on, 145 | trigger: trigger, 146 | }; 147 | 148 | }(my); 149 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js Transcript 6 | 7 | 8 | 9 |
10 |
11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js Transcript Sub Modules 6 | 7 | 8 | 9 |
10 |
11 |
12 |
1
13 |
2
14 |
3
15 |
4
16 |
17 |
18 | 19 |
20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /test/modules.test.js: -------------------------------------------------------------------------------- 1 | /*! videojs-transcript - v0.0.0 - 2014-9-8 2 | * Copyright (c) 2014 Matthew Walsh 3 | * Licensed under the MIT license. */ 4 | /*global my, defaults, eventEmitter, trackList, scroller, widget*/ 5 | (function (window, videojs, qunit) { 6 | 'use strict'; 7 | 8 | var player, 9 | 10 | // local QUnit aliases 11 | // http://api.qunitjs.com/ 12 | 13 | // module(name, {[setup][ ,teardown]}) 14 | module = qunit.module, 15 | // test(name, callback) 16 | test = qunit.test, 17 | // ok(value, [message]) 18 | ok = qunit.ok, 19 | // equal(actual, expected, [message]) 20 | equal = qunit.equal, 21 | // strictEqual(actual, expected, [message]) 22 | strictEqual = qunit.strictEqual, 23 | // deepEqual(actual, expected, [message]) 24 | deepEqual = qunit.deepEqual, 25 | // notEqual(actual, expected, [message]) 26 | notEqual = qunit.notEqual, 27 | // throws(block, [expected], [message]) 28 | throws = qunit.throws, 29 | expect = qunit.expect, 30 | asyncTest = qunit.asyncTest, 31 | Flash = videojs.getComponent('Flash'), 32 | Html5 = videojs.getComponent('Html5'), 33 | backup = { 34 | Flash: { 35 | isSupported: Flash.isSupported 36 | }, 37 | Html5: { 38 | isSupported: Html5.isSupported 39 | } 40 | }; 41 | 42 | /******************** 43 | * test events.js 44 | ********************/ 45 | module('events'); 46 | 47 | test('Register and trigger event', function(assert) { 48 | expect(1); 49 | eventEmitter.on(this, 'test', function() { 50 | assert.ok(true, 'Event was triggered.'); 51 | }); 52 | eventEmitter.trigger(this, 'test'); 53 | }); 54 | 55 | /******************** 56 | * test tracklist.js 57 | ********************/ 58 | module('tracklist', { 59 | setup: function() { 60 | // Force HTML5/Flash support. 61 | Html5.isSupported = Flash.isSupported = function() { 62 | return true; 63 | }; 64 | 65 | // create a video element 66 | var video = document.createElement('video'); 67 | video.setAttribute('id', 'test-video'); 68 | document.querySelector('#qunit-fixture').appendChild(video); 69 | 70 | var track = document.createElement('track'); 71 | track.setAttribute('kind', 'captions'); 72 | track.src = '../captions/captions.en.vtt'; 73 | track.setAttribute('srclang', 'en'); 74 | track.setAttribute('label', 'English'); 75 | video.appendChild(track); 76 | 77 | var track2 = document.createElement('track'); 78 | track2.setAttribute('kind', 'subtitles'); 79 | track2.src = '../captions/captions.sv.vtt'; 80 | track2.setAttribute('srclang', 'sv'); 81 | track2.setAttribute('label', 'Swedish'); 82 | video.appendChild(track2); 83 | 84 | // create a video.js player 85 | player = videojs(video); 86 | 87 | my.player = player; 88 | }, 89 | teardown: function() { 90 | // Restore original state of the techs. 91 | Html5.isSupported = backup.Flash.isSupported; 92 | Flash.isSupported = backup.Html5.setSource; 93 | } 94 | }); 95 | 96 | test('getTracks() returns list of tracks.', function (assert) { 97 | assert.equal(trackList.get().length, videojs.players['test-video'].textTracks().length, 'Tracklist length is correct'); 98 | }); 99 | 100 | asyncTest('active() returns the default track.', function (assert) { 101 | //be sure that video js is ready 102 | setTimeout(function () { 103 | var tracks = trackList.get(); 104 | assert.equal(trackList.active(tracks).label, 'English', 'Active track is defined'); 105 | qunit.start(); 106 | }, 50); 107 | }); 108 | 109 | asyncTest('active() returns active track after track change.', function (assert) { 110 | //be sure that video js is ready 111 | setTimeout(function () { 112 | var tracks = trackList.get(); 113 | videojs.players['test-video'].textTracks()[1].mode = 'showing'; 114 | assert.equal(trackList.active(tracks).label, 'Swedish', 'Active track returns current track after track change.'); 115 | qunit.start(); 116 | }, 50); 117 | }); 118 | 119 | /******************** 120 | * test scroller.js 121 | ********************/ 122 | module('scroller'); 123 | 124 | test('scroller() returns an object.', function (assert) { 125 | var container = document.querySelector('#scrollable-container'); 126 | assert.notEqual(scroller(container), undefined, 'scroller is not undefined.'); 127 | }); 128 | 129 | test('canScroll() returns true when scrollable', function (assert) { 130 | var container = document.querySelector('#scrollable-container'); 131 | container.scroll = scroller(container); 132 | assert.equal(container.scroll.canScroll(), true, 'canScroll returns true on scrollable container.'); 133 | }); 134 | 135 | test('canScroll() returns false when non-scrollable ', function (assert) { 136 | var container = document.querySelector('#non-scrollable-container'); 137 | container.scroll = scroller(container); 138 | assert.equal(container.scroll.canScroll(), false, 'canScroll returns false on non-scrollable container.'); 139 | }); 140 | 141 | asyncTest('to() scrolls to correct location', function (assert) { 142 | expect(2); 143 | var container = document.querySelector('#scrollable-container'); 144 | var item2 = document.querySelector('#item2'); 145 | var item4 = document.querySelector('#item4'); 146 | var initialScrollTop = container.scrollTop; 147 | container.scroll = scroller(container); 148 | container.scroll.to(item4); 149 | setTimeout(function () { 150 | assert.equal(container.scrollTop, initialScrollTop + 100, 'Container scrolls up by 100 pixels to reveal item4' ); 151 | container.scroll.to(item2); 152 | setTimeout(function () { 153 | assert.equal(container.scrollTop, initialScrollTop + 50, 'Container scrolls down by 50 pixels to reveal item2' ); 154 | qunit.start(); 155 | },500); 156 | }, 500); 157 | }); 158 | 159 | }(window, window.videojs, window.QUnit)); 160 | -------------------------------------------------------------------------------- /test/videojs-transcript.test.js: -------------------------------------------------------------------------------- 1 | /*! videojs-transcript - v0.0.0 - 2014-9-8 2 | * Copyright (c) 2014 Matthew Walsh 3 | * Licensed under the MIT license. */ 4 | (function (window, videojs, qunit) { 5 | 'use strict'; 6 | 7 | var player, 8 | 9 | // local QUnit aliases 10 | // http://api.qunitjs.com/ 11 | 12 | // module(name, {[setup][ ,teardown]}) 13 | module = qunit.module, 14 | // test(name, callback) 15 | test = qunit.test, 16 | // ok(value, [message]) 17 | ok = qunit.ok, 18 | // equal(actual, expected, [message]) 19 | equal = qunit.equal, 20 | // strictEqual(actual, expected, [message]) 21 | strictEqual = qunit.strictEqual, 22 | // deepEqual(actual, expected, [message]) 23 | deepEqual = qunit.deepEqual, 24 | // notEqual(actual, expected, [message]) 25 | notEqual = qunit.notEqual, 26 | // throws(block, [expected], [message]) 27 | throws = qunit.throws, 28 | Flash = videojs.getComponent('Flash'), 29 | Html5 = videojs.getComponent('Html5'), 30 | backup = { 31 | Flash: { 32 | isSupported: Flash.isSupported 33 | }, 34 | Html5: { 35 | isSupported: Html5.isSupported 36 | } 37 | }; 38 | 39 | module('videojs-transcript', { 40 | setup: function() { 41 | // Force HTML5/Flash support. 42 | Html5.isSupported = Flash.isSupported = function() { 43 | return true; 44 | }; 45 | 46 | // create a video element 47 | var video = document.createElement('video'); 48 | document.querySelector('#qunit-fixture').appendChild(video); 49 | var track = document.createElement('track'); 50 | video.appendChild(track); 51 | 52 | // create a video.js player 53 | player = videojs(video).ready(function(){ 54 | // initialize the plugin with the default options 55 | this.transcript(); 56 | }); 57 | 58 | }, 59 | teardown: function() { 60 | // Restore original state of the techs. 61 | Html5.isSupported = backup.Flash.isSupported; 62 | Flash.isSupported = backup.Html5.setSource; 63 | } 64 | }); 65 | 66 | test('registers itself', function () { 67 | ok(player.transcript, 'registered the plugin'); 68 | }); 69 | }(window, window.videojs, window.QUnit)); 70 | --------------------------------------------------------------------------------