├── .codeclimate.yml ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── bower.json ├── dist ├── afterglow.min.js └── afterglow.zip ├── gulpfile.js ├── package.json ├── sandbox ├── index.html ├── js │ └── less.min.js ├── releasetest.html ├── releasetest_101.html ├── test_101.html └── text │ ├── .htaccess │ └── subtitles-demo.vtt ├── src ├── js │ ├── afterglow │ │ ├── Afterglow.js │ │ ├── components │ │ │ ├── Config.js │ │ │ ├── Eventbus.js │ │ │ ├── Lightbox.js │ │ │ ├── LightboxTrigger.js │ │ │ └── Player.js │ │ └── lib │ │ │ ├── DOMElement.js │ │ │ └── Util.js │ ├── init.js │ └── vjs-components │ │ ├── LightboxCloseButton.js │ │ ├── ResolutionSwitchingButton.js │ │ └── TopControlBar.js └── less │ ├── components │ └── lightbox.less │ └── skins │ ├── afterglow-dark.less │ ├── afterglow-default.less │ └── afterglow-light.less ├── test ├── test.1.afterglow.js ├── test.2.config.js ├── test.2.lightbox.js ├── test.2.lightbox_trigger.js ├── test.2.player.js ├── test.3.dom-element.js ├── test.3.eventbus.js ├── test.3.util.js └── test.4.emitter.js └── vendor ├── Emitter └── Emitter.js └── videojs ├── plugins ├── Youtube.js ├── videojs-vimeo.js └── videojs.hotkeys.js ├── video-js.css └── video.js /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | eslint: 3 | enabled: true 4 | csslint: 5 | enabled: true 6 | fixme: 7 | enabled: true 8 | 9 | ratings: 10 | paths: 11 | - src/js/** 12 | 13 | exclude_paths: 14 | - vendor/**/* 15 | - sandbox/**/* 16 | - dist/**/* -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "strict": 0 5 | } 6 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moay/afterglow/865a3e83b07af408168d125338fb40e6179863df/.gitattributes -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | coverage/* 3 | 4 | .coverrun 5 | 6 | npm-debug.log 7 | 8 | dist/tmp 9 | 10 | node_modules 11 | \.DS_Store 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | coverage/* 3 | 4 | .coverrun 5 | 6 | npm-debug.log 7 | 8 | dist/tmp 9 | 10 | node_modules 11 | 12 | sandbox 13 | 14 | test 15 | 16 | .codeclimate.yml 17 | 18 | .eslintrc 19 | 20 | .travis.yml 21 | 22 | sftp-config.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1" 4 | - "4.0" 5 | notifications: 6 | slack: afterglowplayer:LzjtRryiNVLXdd7BFsScx289 -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 moay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | Sadly, the development of afterglow has been pretty stale for some time now. There are currently no plans for further developement regarding this project, so it has to be considered deprecated. 3 | 4 | We strongly recommend using [Plyr](https://github.com/sampotts/plyr) instead. If you need a lightbox player, consider [GLightbox](https://github.com/biati-digital/glightbox) which integrates with Plyr. 5 | 6 | --- 7 | [![GitHub version](https://badge.fury.io/gh/moay%2Fafterglow.svg)](http://badge.fury.io/gh/moay%2Fafterglow) [![Build status](https://travis-ci.org/moay/afterglow.svg?branch=master)](https://travis-ci.org/moay/afterglow) [![Coverage Status](https://coveralls.io/repos/moay/afterglow/badge.svg?branch=master&service=github)](https://coveralls.io/github/moay/afterglow?branch=master) 8 | 9 | 10 | # afterglow 11 | 12 | afterglow is a tool to create fully responsive and totally awesome video players from HTML5 video elements with as little effort as possible. 13 | 14 | Learn more about the project on the project website: [http://afterglowplayer.com](http://afterglowplayer.com). 15 | 16 | ## Documentation 17 | 18 | Documentation is still available here: [docs.afterglowplayer.com](http://docs.afterglowplayer.com). 19 | 20 | ## Credits 21 | 22 | afterglow relies on scripts provided by many great people. 23 | 24 | - [video.js](http://www.videojs.com/) published under the [Apache License 2.0](https://github.com/videojs/video.js/blob/master/LICENSE) 25 | - [videojs-youtube](https://github.com/eXon/videojs-youtube) published under the [MIT License](https://github.com/eXon/videojs-youtube/blob/master/LICENSE) 26 | - [videojs-vimeo](https://github.com/eXon/videojs-vimeo) published under the [MIT License](https://github.com/eXon/videojs-vimeo/blob/master/LICENSE) 27 | - [videojs-hotkeys](https://github.com/ctd1500/videojs-hotkeys) published under the [Apache License 2.0](https://github.com/ctd1500/videojs-hotkeys/blob/master/LICENSE.md) 28 | - [Emitter](https://github.com/component/emitter) published under the [MIT License](https://github.com/component/emitter/blob/master/LICENSE) 29 | - The font [Open Sans](https://www.google.com/fonts/specimen/Open+Sans) published under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) 30 | 31 | Our resolution switching component is based on videojs-resolutions by vidcaster published under the Apache License 2.0. 32 | 33 | Thanks for your great work, guys! 34 | 35 | ## Thank you! 36 | 37 | ![browserstack](https://afterglowplayer.com/images/browserstack.png) 38 | 39 | We want to thank the people at [browserstack](https://browserstack.com) who provide us with a free account for browser testing. 40 | 41 | The [afterglow documentation](http://docs.afterglowplayer.com) would not be that cool without [readme.io](http://readme.io), who also sponsor the project with a free account. 42 | 43 | ![jsDelivr](https://afterglowplayer.com/images/jsdelivr-logo.png) 44 | 45 | Finally, a big thank you to the team behind [jsDelivr](http://jsdelivr.com) for hosting afterglow reliably. 46 | 47 | ## Copyright and License 48 | 49 | Copyright moay under the [MIT license](LICENSE.md). 50 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afterglow", 3 | "description": "An easy to integrate HTML5 video player with lightbox support.", 4 | "main": "dist/afterglow.min.js", 5 | "authors": [ 6 | "moay " 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "html5 video", 11 | "video", 12 | "video.js", 13 | "player", 14 | "youtube", 15 | "vimeo", 16 | "lightbox" 17 | ], 18 | "homepage": "http://afterglowplayer.com", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "tests", 25 | "sandbox" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /dist/afterglow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moay/afterglow/865a3e83b07af408168d125338fb40e6179863df/dist/afterglow.zip -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('babel/register'); 4 | 5 | var plugins = require('gulp-load-plugins')(); 6 | var gulp = require('gulp'); 7 | var del = require('del'); 8 | var fs = require('fs'); 9 | var browserify = require('browserify'); 10 | var babelify = require("babelify"); 11 | var release = require('gulp-github-release'); 12 | var notifierReporter = require('mocha-notifier-reporter'); 13 | var getPackageJson = function () { 14 | return JSON.parse(fs.readFileSync('./package.json', 'utf8')); 15 | }; 16 | 17 | // Shortcut to the build task 18 | gulp.task('build', ['package-build'], function(){}); 19 | 20 | // General build task, cleans up after real build 21 | gulp.task('cleanup-tmp',['build-afterglow'], function(){ 22 | return del(['./dist/tmp']); 23 | }); 24 | 25 | gulp.task('package-build', ['cleanup-tmp'], function(){ 26 | return gulp.src('./dist/afterglow.min.js') 27 | .pipe(plugins.zip('afterglow.zip')) 28 | .pipe(gulp.dest('dist')); 29 | }); 30 | 31 | // Helper task for building the release 32 | gulp.task('build-afterglow', ['compileES6'], function(){ 33 | 34 | var pkg = getPackageJson(); 35 | var banner = ['/**', 36 | ' * <%= pkg.name %> - <%= pkg.description %>', 37 | ' * @link <%= pkg.homepage %>', 38 | ' * @version <%= pkg.version %>', 39 | ' * @license <%= pkg.license %>', 40 | ' * ', 41 | ' * <%= pkg.name %> includes some scripts provided under different licenses by their authors. Please see the project sources via <%= pkg.homepage %> in order to learn which projects are included and how you may use them.', 42 | ' */', 43 | ''].join('\n'); 44 | 45 | // Loading LESS files 46 | return gulp.src([ 47 | "./src/less/skins/*.less", 48 | "./src/less/components/*.less" 49 | ]) 50 | 51 | // Convert LESS files to CSS 52 | .pipe(plugins.less()) 53 | 54 | // Add normal css which doesn't need to be compiled 55 | .pipe(plugins.addSrc.prepend('./vendor/videojs/video-js.css')) 56 | 57 | // Minify the CSS 58 | .pipe(plugins.cssmin()) 59 | 60 | // Now convert it to JavaScript and specify options 61 | .pipe(plugins.css2js({ 62 | splitOnNewline: false 63 | })) 64 | 65 | // Add all the javascript files in the correct order 66 | .pipe(plugins.addSrc.append([ 67 | './vendor/videojs/video.js', 68 | ])) 69 | .pipe(plugins.addSrc.append([ 70 | './vendor/videojs/plugins/videojs.hotkeys.js', 71 | './vendor/videojs/plugins/Youtube.js', 72 | './vendor/videojs/plugins/videojs-vimeo.js' 73 | ])) 74 | .pipe(plugins.addSrc.append([ 75 | './dist/tmp/afterglow-bundle.js' 76 | ])) 77 | 78 | // Concatenate into a single large file 79 | .pipe(plugins.concat("afterglow.min.js")) 80 | 81 | // Minify the JavaScript 82 | .pipe(plugins.uglify().on('error', plugins.util.log)) 83 | 84 | .pipe(plugins.header(banner, { pkg : pkg } )) 85 | 86 | // Finally write it to our destination (./dist/afterglow.min.js) 87 | .pipe(gulp.dest("./dist/")); 88 | }); 89 | 90 | // Task to compile ES6 components 91 | gulp.task('compileES6',['compileVJSComponents'], function(){ 92 | // Create empty file 93 | gulp.src(__dirname+'/dist/tmp/components/*.js') 94 | .pipe(plugins.concat("afterglow-bundle.js")) 95 | .pipe(gulp.dest(__dirname+"/dist/tmp/")); 96 | 97 | // Compile 98 | var extensions = ['.js','.json','.es6']; 99 | return browserify({ debug: true, extensions:extensions }) 100 | .transform(babelify.configure({ 101 | extensions: extensions 102 | })) 103 | .require(__dirname+"/src/js/init.js", { entry: true }) 104 | .bundle() 105 | .on("error", function (err) { console.log("Error : " + err.message); }) 106 | .pipe(fs.createWriteStream(__dirname+"/dist/tmp/afterglow-bundle.js",{flags: 'a'})); 107 | }); 108 | 109 | gulp.task('compileVJSComponents', function(){ 110 | // Compile VIDEO.js components 111 | return gulp.src('./src/js/vjs-components/*.js') 112 | .pipe(plugins.babel()) 113 | .pipe(gulp.dest(__dirname+'/dist/tmp/components')); 114 | }) 115 | 116 | // Patch version bump 117 | gulp.task('bump', function(){ 118 | return gulp.src('.') 119 | .pipe(plugins.prompt.prompt({ 120 | type: 'list', 121 | name: 'bump', 122 | message: 'What type of bump would you like to do?', 123 | choices: ['cancel','patch', 'minor', 'major'] 124 | }, function(res){ 125 | if(res.bump == 'cancel'){ 126 | plugins.util.log(plugins.util.colors.red('Version bump canceled.')); 127 | } 128 | else{ 129 | gulp.src('./package.json') 130 | .pipe(plugins.bump({type:res.bump})) 131 | .pipe(gulp.dest('./')); 132 | } 133 | })); 134 | }); 135 | 136 | // Release to github 137 | gulp.task('release', function(){ 138 | var pkg = getPackageJson(); 139 | 140 | gulp.src('.') 141 | .pipe(plugins.prompt.confirm({ 142 | message: 'Did you commit and push/sync all changes you made to the code?', 143 | default: false 144 | })) 145 | .pipe(plugins.prompt.prompt([{ 146 | type: 'input', 147 | name: 'releasename', 148 | message: 'How shall the release be named?', 149 | default: ['afterglow v'+pkg.version] 150 | }, 151 | { 152 | type: 'input', 153 | name: 'notes', 154 | message: 'Provide a release note, if you want to.' 155 | }, 156 | { 157 | type: 'list', 158 | name: 'type', 159 | message: 'What do you want to release?', 160 | choices: ['Prerelease','Release'], 161 | default: 1, 162 | }],function(res){ 163 | 164 | // Build the options object 165 | var releaseoptions = { 166 | name: res.releasename, 167 | notes: res.notes, 168 | manifest: require('./package.json'), 169 | owner: 'moay', 170 | repo: 'afterglow', 171 | tag: pkg.version, 172 | draft: true 173 | }; 174 | if(res.type == "Prerelease") 175 | { 176 | releaseoptions.prerelease = true; 177 | } 178 | 179 | // LAST CHANGE TO CANCEL NOTICE 180 | plugins.util.log(''); 181 | plugins.util.log('Your are going to release', plugins.util.colors.yellow(res.releasename), plugins.util.colors.cyan('(Version tag: '+pkg.version+')'), 'as a', plugins.util.colors.white(res.type)); 182 | 183 | 184 | // Remember to set an env var called GITHUB_TOKEN 185 | gulp.src('./dist/afterglow.zip') 186 | .pipe(plugins.prompt.confirm({ 187 | message: 'Do you really want this? Last chance!', 188 | default: false 189 | })) 190 | .pipe(release(releaseoptions)); 191 | })); 192 | }); 193 | 194 | gulp.task('test', function(){ 195 | return gulp.src('./test/*.js') 196 | .pipe(plugins.mocha({ 197 | reporter: notifierReporter.decorate('spec') 198 | })); 199 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afterglowplayer", 3 | "description": "An easy to integrate HTML5 video player with lightbox support.", 4 | "version": "1.1.0", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/moay/afterglow.git" 9 | }, 10 | "jsdelivr": "dist/afterglow.min.js", 11 | "homepage": "http://afterglowplayer.com", 12 | "devDependencies": { 13 | "append-stream": "^1.2.2", 14 | "babel": "^5.8.23", 15 | "babelify": "^6.3.0", 16 | "browserify": "^11.1.0", 17 | "chai": "^3.2.0", 18 | "coveralls": "^2.11.4", 19 | "del": "^1.2.1", 20 | "gulp": "^3.9.1", 21 | "gulp-add-src": "~0.2.0", 22 | "gulp-babel": "^5.2.1", 23 | "gulp-bump": "^0.3.1", 24 | "gulp-concat": "*", 25 | "gulp-coverage": "^0.3.36", 26 | "gulp-css2js": "*", 27 | "gulp-cssmin": "*", 28 | "gulp-file": "^0.2.0", 29 | "gulp-filter": "~3.0.1", 30 | "gulp-git": "~1.2.4", 31 | "gulp-github-release": "^1.0.3", 32 | "gulp-header": "~1.5.0", 33 | "gulp-istanbul": "^0.10.1", 34 | "gulp-less": "*", 35 | "gulp-load-plugins": "~1.0.0-rc.1", 36 | "gulp-mocha": "^2.1.3", 37 | "gulp-prompt": "^0.1.2", 38 | "gulp-uglify": "^1.3.0", 39 | "gulp-util": "^3.0.6", 40 | "gulp-zip": "~3.0.2", 41 | "isparta": "^3.0.4", 42 | "istanbul": "^0.3.21", 43 | "istanbul-traceur": "^1.0.7", 44 | "jquery": "^2.1.4", 45 | "jsdom": "^6.5.0", 46 | "karma": "^0.13.10", 47 | "mocha": "^2.3.2", 48 | "mocha-jsdom": "^1.0.0", 49 | "mocha-notifier-reporter": "^0.1.1", 50 | "mockery": "^1.4.0", 51 | "sinon": "^1.16.1", 52 | "sinon-chai": "^2.8.0", 53 | "traceur": "0.0.91" 54 | }, 55 | "scripts": { 56 | "test": "babel-node ./node_modules/.bin/isparta cover _mocha -- --reporter dot && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage", 57 | "coverage": "babel-node ./node_modules/.bin/isparta cover --report text --report html _mocha -- --reporter dot" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /sandbox/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | afterglow development sandbox 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 |

This is just a sandbox for development.

34 | 35 |

Youtube video

36 | 37 | 38 | 39 |

Local video, single source, with poster and bound play event

40 | 41 | 42 | 43 |

Local video with poster and resolution test, dark skin

44 | 54 | 55 |

Local video with poster and resolution test light skin

56 | 61 | 62 | 63 | 64 |

Vimeo video

65 | 66 | 67 | 68 | 69 |

Local video with poster opened in lightbox

70 | Open the lightbox 71 | 76 | 77 |

Youtube opened in lightbox

78 | Open the lightbox 79 | 80 | 81 |

Vimeo opened in lightbox

82 | Open the lightbox 83 | 84 | 85 | 108 | 109 | -------------------------------------------------------------------------------- /sandbox/releasetest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | afterglow development sandbox 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |

This is just a sandbox for development.

20 | 21 | 22 |

Youtube video

23 | 24 |

Vimeo video

25 | 26 | 27 | 28 |

Local video with poster

29 | 34 | 39 | 44 | 45 |

Local video with poster opened in lightbox

46 | Open the lightbox 47 | 51 | Open the lightbox (dark) 52 | 56 | Open the lightbox (light) 57 | 61 | 62 | 63 | 64 | 65 |

Youtube opened in lightbox

66 | Open the lightbox 67 | 68 | 69 | 70 |

Vimeo opened in lightbox

71 | Open the lightbox 72 | 73 | 74 | -------------------------------------------------------------------------------- /sandbox/releasetest_101.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | afterglow development sandbox 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |

This is just a sandbox for development.

20 | 21 | 22 | 23 | 24 |

Youtube opened in lightbox

25 | Open the lightbox 26 | 27 | 28 | 29 |

Vimeo opened in lightbox

30 | Open the lightbox 31 | 32 | 33 | -------------------------------------------------------------------------------- /sandbox/test_101.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | afterglow development sandbox 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 30 | 31 | 32 | 33 |

This is just a sandbox for development.

34 | 35 | 36 |

Local video, single source, with poster and bound play event

37 | Test 2 38 | 39 | 40 | 41 | 42 | 43 | 44 |

Vimeo opened in lightbox

45 | Open the lightbox 46 | 47 | 48 | -------------------------------------------------------------------------------- /sandbox/text/.htaccess: -------------------------------------------------------------------------------- 1 | AddType text/vtt .vtt -------------------------------------------------------------------------------- /sandbox/text/subtitles-demo.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT FILE 2 | 3 | 1 4 | 00:00:00.500 --> 00:00:05.000 5 | This is a subtitle. 6 | 7 | 2 8 | 00:00:06.500 --> 00:00:11.300 9 | Wonderful. Isn't it? -------------------------------------------------------------------------------- /src/js/afterglow/Afterglow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * afterglow - An easy to integrate HTML5 video player with lightbox support. 3 | * @link http://afterglowplayer.com 4 | * @license MIT 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Player from './components/Player'; 10 | import Lightbox from './components/Lightbox'; 11 | import LightboxTrigger from './components/LightboxTrigger'; 12 | import Eventbus from './components/Eventbus'; 13 | import DOMElement from './lib/DOMElement'; 14 | 15 | 16 | class Afterglow { 17 | 18 | constructor(){ 19 | /** 20 | * Will hold the players in order to make them accessible 21 | */ 22 | this.players = []; 23 | 24 | /** 25 | * Will hold the trigger elements which will launch lightbox players 26 | */ 27 | this.lightboxtriggers = []; 28 | 29 | /** 30 | * Will hold the event bus which registers and dispatches events 31 | */ 32 | this.eventbus = false; 33 | 34 | /** 35 | * Container for callbacks that have to be executed when afterglow is initalized 36 | */ 37 | this.afterinit = []; 38 | } 39 | 40 | /** 41 | * Initiate all players that were found and need to be initiated 42 | * @return void 43 | */ 44 | init(){ 45 | // Run some preparations 46 | this.configureVideoJS(); 47 | 48 | // prepare the event bus 49 | this.prepareEventbus(); 50 | 51 | // initialize regular players 52 | this.initVideoElements(); 53 | 54 | // prepare Lightboxes 55 | this.prepareLightboxVideos(); 56 | 57 | // execute things to do when init done 58 | for (var i = 0; i < this.afterinit.length; i++) { 59 | this.afterinit[i](); 60 | } 61 | this.afterinit = []; 62 | } 63 | 64 | /** 65 | * Looks for players to initiate and creates AfterglowPlayer objects based on those elements 66 | * @return void 67 | */ 68 | initVideoElements(){ 69 | // Get players including sublime fallback 70 | var players = document.querySelectorAll("video.afterglow,video.sublime"); 71 | 72 | // Initialize players 73 | for (var i = 0; i < players.length; i++){ 74 | let videoelement = new DOMElement(players[i]); 75 | var player = new Player(videoelement); 76 | player.init(); 77 | this.players.push(player); 78 | } 79 | } 80 | 81 | /** 82 | * Prepares all found trigger elements and makes them open their corresponding players when needed 83 | * @return void 84 | */ 85 | prepareLightboxVideos(){ 86 | // Get lightboxplayers including sublime fallback 87 | var lightboxtriggers = document.querySelectorAll("a.afterglow,a.sublime"); 88 | 89 | // Initialize players launching in a lightbox 90 | for (var i = 0; i < lightboxtriggers.length; i++){ 91 | let trigger = new LightboxTrigger(lightboxtriggers[i]); 92 | 93 | this.bindLightboxTriggerEvents(trigger); 94 | 95 | this.lightboxtriggers.push(trigger); 96 | } 97 | } 98 | 99 | /** 100 | * Initializes the event bus 101 | * @return void 102 | */ 103 | prepareEventbus() { 104 | // Reset the event bus 105 | this.eventbus = false; 106 | this.eventbus = new Eventbus(); 107 | } 108 | 109 | /** 110 | * Binds an event for any given player 111 | * 112 | * @param string playerid The playerid 113 | * @param string eventname The eventname 114 | * @param mixed _callback The callback 115 | */ 116 | on(playerid, eventname, _callback){ 117 | if(!this.eventbus){ 118 | this.afterinit.push(() => { 119 | this.on(playerid, eventname, _callback); 120 | }); 121 | } 122 | else{ 123 | this.eventbus.subscribe(playerid, eventname, _callback); 124 | } 125 | } 126 | 127 | /** 128 | * Removes an event for any given player 129 | * 130 | * @param string playerid The playerid 131 | * @param string eventname The eventname 132 | * @param mixed _callback The callback 133 | */ 134 | off(playerid, eventname, _callback){ 135 | if(!this.eventbus){ 136 | this.afterinit.push(() => { 137 | this.off(playerid, eventname, _callback); 138 | }); 139 | } 140 | else{ 141 | this.eventbus.unsubscribe(playerid, eventname, _callback); 142 | } 143 | } 144 | 145 | /** 146 | * Binds some elements for lightbox triggers. 147 | * @param {object} the trigger object 148 | * @return void 149 | */ 150 | bindLightboxTriggerEvents(trigger){ 151 | trigger.on('trigger',() => { 152 | this.consolidatePlayers; 153 | }); 154 | trigger.on('close',() => { 155 | this.consolidatePlayers(); 156 | }); 157 | } 158 | 159 | /** 160 | * Shortcut method to trigger a player's play method. Will launch lightboxes if needed. 161 | * 162 | * @param {playerid} the player's id 163 | * @return void 164 | */ 165 | play(playerid){ 166 | // Look out for regular player 167 | for (var i = this.players.length - 1; i >= 0; i--) { 168 | if(this.players[i].id === playerid){ 169 | this.players[i].getPlayer().play(); 170 | } 171 | }; 172 | // Else try to trigger lightbox player 173 | for (var i = this.lightboxtriggers.length - 1; i >= 0; i--) { 174 | if(this.lightboxtriggers[i].playerid === playerid){ 175 | this.lightboxtriggers[i].trigger(); 176 | } 177 | }; 178 | } 179 | 180 | /** 181 | * Returns the the players object if it was initiated yet 182 | * @param string The player's id 183 | * @return boolean false or object if found 184 | */ 185 | getPlayer(playerid){ 186 | // Try to get regular player 187 | for (var i = this.players.length - 1; i >= 0; i--) { 188 | if(this.players[i].id === playerid){ 189 | return this.players[i].getPlayer(); 190 | } 191 | }; 192 | // Else try to find lightbox player 193 | for (var i = this.lightboxtriggers.length - 1; i >= 0; i--) { 194 | if(this.lightboxtriggers[i].playerid === playerid){ 195 | return this.lightboxtriggers[i].getPlayer(); 196 | } 197 | }; 198 | return false; 199 | } 200 | 201 | /** 202 | * Should destroy a player instance if it exists. Lightbox players should be just closed. 203 | * @param {string} playerid The player's id 204 | * @return void 205 | */ 206 | destroyPlayer(playerid){ 207 | // Look for regular players 208 | for (var i = this.players.length - 1; i >= 0; i--) { 209 | if(this.players[i].id === playerid){ 210 | this.players[i].destroy(); 211 | this.players.splice(i,1); 212 | return true; 213 | } 214 | }; 215 | // Else look for an active lightbox 216 | for (var i = this.lightboxtriggers.length - 1; i >= 0; i--) { 217 | if(this.lightboxtriggers[i].playerid === playerid){ 218 | this.closeLightbox(); 219 | return true; 220 | } 221 | }; 222 | return false; 223 | } 224 | 225 | /** 226 | * Closes the lightbox and resets the lightbox player so that it can be reopened 227 | * @return void 228 | */ 229 | closeLightbox(){ 230 | for (var i = this.lightboxtriggers.length - 1; i >= 0; i--) { 231 | this.lightboxtriggers[i].closeLightbox(); 232 | }; 233 | this.consolidatePlayers(); 234 | } 235 | 236 | /** 237 | * Consolidates the players container and removes players that are not alive any more. 238 | * @return {[type]} [description] 239 | */ 240 | consolidatePlayers(){ 241 | for (var i = this.players.length - 1; i >= 0; i--) { 242 | if(this.players[i] !== undefined && !this.players[i].alive){ 243 | delete this.players[i]; 244 | 245 | // Reset indexes 246 | this.players = this.players.filter(() =>{return true}); 247 | } 248 | }; 249 | } 250 | 251 | /** 252 | * Run some configurations on video.js to make it work for us 253 | * @return void 254 | */ 255 | configureVideoJS(){ 256 | // Disable tracking 257 | window.HELP_IMPROVE_VIDEOJS = false; 258 | } 259 | } 260 | 261 | export default Afterglow; -------------------------------------------------------------------------------- /src/js/afterglow/components/Config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * afterglow - An easy to integrate HTML5 video player with lightbox support. 3 | * @link http://afterglowplayer.com 4 | * @license MIT 5 | */ 6 | 'use strict'; 7 | 8 | import Util from '../lib/Util'; 9 | 10 | class Config { 11 | 12 | constructor(videoelement, skin = 'afterglow'){ 13 | return this.init(videoelement, skin); 14 | } 15 | 16 | init(videoelement, skin = 'afterglow'){ 17 | 18 | // Check for the video element 19 | if(videoelement == undefined){ 20 | console.error('Please provide a proper video element to afterglow'); 21 | } 22 | else{ 23 | // Set videoelement 24 | this.videoelement = videoelement; 25 | 26 | // Prepare the options container 27 | this.options = {}; 28 | 29 | // Set the skin 30 | this.skin = skin; 31 | 32 | // Prepare option variables 33 | this.setDefaultOptions(); 34 | this.setSkinControls(); 35 | 36 | let util = new Util; 37 | // Initialize youtube if the current player is a youtube player 38 | if(util.isYoutubePlayer(this.videoelement)){ 39 | this.setYoutubeOptions(); 40 | } 41 | // Initialize vimeo if the current player is a vimeo player 42 | if(util.isVimeoPlayer(this.videoelement)){ 43 | this.setVimeoOptions(); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * Sets some basic options based on the videoelement's attributes 50 | * @return {void} 51 | */ 52 | setDefaultOptions(){ 53 | // Controls needed for the player 54 | this.options.controls = true; 55 | 56 | // Default tech order 57 | this.options.techOrder = ["Html5"]; 58 | 59 | // Some default player parameters 60 | this.options.preload = this.getPlayerAttributeFromVideoElement('preload','auto'); 61 | this.options.autoplay = this.getPlayerAttributeFromVideoElement('autoplay'); 62 | this.options.poster = this.getPlayerAttributeFromVideoElement('poster'); 63 | } 64 | 65 | /** 66 | * Gets a configuration value that has been passed to the videoelement as HTML tag attribute 67 | * @param {string} attributename The name of the attribute to get 68 | * @param {mixed} fallback The expected fallback if the attribute was not set - false by default 69 | * @return {mixed} The attribute (with data-attributename being preferred) or the fallback if none. 70 | */ 71 | getPlayerAttributeFromVideoElement(attributename, fallback = false){ 72 | if(this.videoelement.getAttribute("data-"+attributename) !== null){ 73 | return this.videoelement.getAttribute("data-"+attributename); 74 | } else if(this.videoelement.getAttribute(attributename) !== null){ 75 | return this.videoelement.getAttribute(attributename); 76 | } else { 77 | return fallback; 78 | } 79 | } 80 | 81 | /** 82 | * Sets the controls which are needed for the player to work properly. 83 | */ 84 | setSkinControls(){ 85 | // For now, we just output the default 'afterglow' skin children, as there isn't any other skin defined yet 86 | let controlBar = { 87 | children: [ 88 | { 89 | name: "currentTimeDisplay" 90 | }, 91 | { 92 | name: "playToggle" 93 | }, 94 | { 95 | name: "durationDisplay" 96 | }, 97 | { 98 | name: "progressControl" 99 | }, 100 | { 101 | name: "ResolutionSwitchingButton" 102 | }, 103 | { 104 | name: "volumeMenuButton", 105 | inline:true 106 | }, 107 | { 108 | name: "subtitlesButton" 109 | }, 110 | { 111 | name: "captionsButton" 112 | } 113 | ] 114 | }; 115 | this.options.controlBar = controlBar; 116 | } 117 | 118 | /** 119 | * Sets options needed for youtube to work and replaces the sources with the correct youtube source 120 | */ 121 | setYoutubeOptions(){ 122 | this.options.showinfo = 0; 123 | this.options.techOrder = ["youtube"]; 124 | this.options.sources = [{ 125 | "type": "video/youtube", 126 | "src": "https://www.youtube.com/watch?v="+this.getPlayerAttributeFromVideoElement('youtube-id') 127 | }]; 128 | 129 | let util = new Util; 130 | if(util.ie().actualVersion >= 8 && util.ie().actualVersion <= 11){ 131 | this.options.youtube = { 132 | ytControls : 2, 133 | color : "white", 134 | modestbranding : 1 135 | }; 136 | } 137 | else{ 138 | this.options.youtube = { 139 | 'iv_load_policy' : 3, 140 | modestbranding: 1 141 | }; 142 | } 143 | } 144 | 145 | /** 146 | * Sets options needed for vimeo to work and replaces the sources with the correct vimeo source 147 | */ 148 | setVimeoOptions(){ 149 | this.options.techOrder = ["vimeo"]; 150 | this.options.sources = [{ 151 | "type": "video/vimeo", 152 | "src": "https://vimeo.com/"+this.getPlayerAttributeFromVideoElement('vimeo-id') 153 | }]; 154 | } 155 | 156 | /** 157 | * Returns the CSS class for the video element 158 | * @return {string} 159 | */ 160 | getSkinClass(){ 161 | var cssclass="vjs-afterglow-skin"; 162 | if(this.skin !== 'afterglow'){ 163 | cssclass += " afterglow-skin-"+this.skin; 164 | } 165 | 166 | // Fix for IE9. Somehow, this is necessary. Won't hurt anyone, so this hack is installed. 167 | let util = new Util; 168 | if(util.ie().actualVersion == 9){ 169 | cssclass += ' ie9-is-bad'; 170 | } 171 | 172 | return cssclass; 173 | } 174 | } 175 | 176 | export default Config; -------------------------------------------------------------------------------- /src/js/afterglow/components/Eventbus.js: -------------------------------------------------------------------------------- 1 | /** 2 | * afterglow - An easy to integrate HTML5 video player with lightbox support. 3 | * @link http://afterglowplayer.com 4 | * @license MIT 5 | */ 6 | 'use strict'; 7 | 8 | 9 | class Eventbus{ 10 | 11 | constructor() { 12 | this.players = {}; 13 | } 14 | 15 | /** 16 | * Packs the events into the container to be able to listen to them lateron 17 | * 18 | * @param string playerid The playerid 19 | * @param string event The event 20 | * @param executable _callback The callback 21 | */ 22 | subscribe(playerid, event, _callback) { 23 | if(!this.players[playerid]){ 24 | this.players[playerid] = { listeners : {} }; 25 | } 26 | if(!this.players[playerid].listeners[event]){ 27 | this.players[playerid].listeners[event] = []; 28 | } 29 | this.players[playerid].listeners[event].push(_callback); 30 | } 31 | 32 | /** 33 | * Removes the the first matching callback which was bound to the event 34 | * 35 | * @param string playerid The playerid 36 | * @param string event The event 37 | * @param executable _callback The callback 38 | */ 39 | unsubscribe(playerid, event, _callback) { 40 | if(!this.players[playerid] || !this.players[playerid].listeners[event] || this.players[playerid].listeners[event].indexOf(_callback) === -1){ 41 | console.error('afterglow could not unbind your event because the _callback was not bound'); 42 | } 43 | else { 44 | let index = this.players[playerid].listeners[event].indexOf(_callback); 45 | this.players[playerid].listeners[event].splice(index,1); 46 | } 47 | } 48 | 49 | /** 50 | * Dispatches an event and executes all bound callbacks 51 | * 52 | * @param string playerid The playerid 53 | * @param string event The event 54 | * @param executable _callback The callback 55 | */ 56 | dispatch(playerid, event) { 57 | if(!this.players[playerid] || !this.players[playerid].listeners[event]){ 58 | return false; 59 | } 60 | for (var i = 0; i < this.players[playerid].listeners[event].length; i++) { 61 | this.players[playerid].listeners[event][i]({type: event, playerid: playerid, player: window.afterglow.getPlayer(playerid)}); 62 | } 63 | } 64 | } 65 | 66 | export default Eventbus; -------------------------------------------------------------------------------- /src/js/afterglow/components/Lightbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * afterglow - An easy to integrate HTML5 video player with lightbox support. 3 | * @link http://afterglowplayer.com 4 | * @license MIT 5 | */ 6 | 'use strict'; 7 | 8 | import Player from './Player'; 9 | import Util from '../lib/Util'; 10 | import DOMElement from '../lib/DOMElement'; 11 | 12 | // For emitting and receiving events 13 | import Emitter from '../../../../vendor/Emitter/Emitter'; 14 | 15 | class Lightbox extends DOMElement{ 16 | 17 | constructor(){ 18 | super(document.createElement('div')); 19 | this.addClass("afterglow-lightbox-wrapper"); 20 | this.build(); 21 | this.bindEmitter(); 22 | } 23 | 24 | /** 25 | * Prepares the lightbox elements which are needed to properly add them to the DOM 26 | * @return {void} 27 | */ 28 | build(){ 29 | // Prepare the lightbox elements 30 | let cover = this.buildCover(); 31 | let lightbox = this.buildLightbox(); 32 | 33 | this.appendDomElement(cover, 'cover'); 34 | this.appendDomElement(lightbox, 'lightbox'); 35 | } 36 | 37 | /** 38 | * Builds the Cover element 39 | * @return {DOMElement object} 40 | */ 41 | buildCover(){ 42 | var cover = document.createElement('div'); 43 | cover = new DOMElement(cover); 44 | cover.addClass("cover"); 45 | return cover; 46 | } 47 | 48 | /** 49 | * Builds the Lightbox element 50 | * @return {DOMElement object} 51 | */ 52 | buildLightbox(){ 53 | var lightbox = document.createElement('div'); 54 | lightbox = new DOMElement(lightbox); 55 | lightbox.addClass("afterglow-lightbox"); 56 | return lightbox; 57 | } 58 | 59 | /** 60 | * Initiating the Lightbox and enabling element binding 61 | * @return void 62 | */ 63 | bindEmitter(){ 64 | Emitter(this); 65 | } 66 | 67 | /** 68 | * Appends the real videoElement to the wrapper. 69 | * @param {[type]} videoelement [description] 70 | * @return {[type]} [description] 71 | */ 72 | passVideoElement(videoelement){ 73 | this.playerid = videoelement.getAttribute("id"); 74 | // This is not easily testable. But the constructor of DOMElement is so simple, that we rely on the UTs that exist for DOMElement. 75 | videoelement = new DOMElement(videoelement); 76 | this.lightbox.appendDomElement(videoelement, 'videoelement'); 77 | this.lightbox.videoelement = videoelement; 78 | this.lightbox.videoelement.setAttribute("autoplay","autoplay"); 79 | 80 | this.player = new Player(this.lightbox.videoelement); 81 | } 82 | 83 | /** 84 | * Method which will actually launch the player. Nodes will be appended to the DOM and all events will be bound. 85 | * @param {closure} _callback A callback function which will be executed after having completed the launch if needed. 86 | * @return {void} 87 | */ 88 | launch(_callback){ 89 | var util = new Util; 90 | document.body.appendChild(this.node); 91 | 92 | this.player.init(() => { 93 | 94 | // Prevent autoplay for mobile devices, won't work anyways... 95 | if(!util.isMobile()){ 96 | // If autoplay didn't work 97 | if(this.player.videojs.paused()){ 98 | this.player.videojs.posterImage.show(); 99 | this.player.videojs.bigPlayButton.show(); 100 | } 101 | } 102 | 103 | // Adding autoclose functionality 104 | if(this.lightbox.videoelement.getAttribute("data-autoclose") == "true"){ 105 | this.player.videojs.on('ended', () => { 106 | this.close(); 107 | }); 108 | } 109 | // Else show the poster frame on ended. 110 | else{ 111 | this.player.videojs.on('ended', () => { 112 | this.player.videojs.posterImage.show(); 113 | }); 114 | } 115 | 116 | this.player.videojs.getChild('TopControlBar').addChild("LightboxCloseButton"); 117 | }); 118 | 119 | // Stop all active players if there are any playing 120 | for(let key in window.videojs.getPlayers()) { 121 | if(window.videojs.getPlayers()[key] !== null && window.videojs.getPlayers()[key].id_ !== this.playerid){ 122 | window.videojs.getPlayers()[key].pause(); 123 | } 124 | } 125 | 126 | // resize the lightbox and make it autoresize 127 | this.resize(); 128 | util.addEventListener(window,'resize',() => { 129 | this.resize(); 130 | }); 131 | 132 | // bind the closing event 133 | this.cover.bind('click',() => { 134 | this.close(); 135 | }); 136 | 137 | // bind the escape key 138 | util.addEventListener(window,'keyup',(e) => { 139 | if(e.keyCode == 27) 140 | { 141 | this.close(); 142 | } 143 | }); 144 | 145 | // Launch the callback if there is one 146 | if(typeof _callback == "function"){ 147 | _callback(this); 148 | } 149 | } 150 | 151 | /** 152 | * Resize the lightbox according to the media ratio 153 | * @return void 154 | */ 155 | resize(){ 156 | // Standard HTML5 player 157 | if(this.lightbox.videoelement !== undefined){ 158 | var ratio = this.lightbox.videoelement.getAttribute("data-ratio"); 159 | if(this.lightbox.videoelement.getAttribute("data-overscale") == "false") 160 | { 161 | // Calculate the new size of the player with maxwidth 162 | var sizes = this.calculateLightboxSizes(ratio, parseInt(this.lightbox.videoelement.getAttribute("data-maxwidth"))); 163 | } 164 | else{ 165 | // Calculate the new size of the player without maxwidth 166 | var sizes = this.calculateLightboxSizes(ratio); 167 | } 168 | } 169 | else{ 170 | // Youtube 171 | if(document.querySelectorAll("div.afterglow-lightbox-wrapper .vjs-youtube").length == 1){ 172 | var playerelement = document.querySelector("div.afterglow-lightbox-wrapper .vjs-youtube"); 173 | var ratio = playerelement.getAttribute("data-ratio"); 174 | var sizes = this.calculateLightboxSizes(ratio); 175 | } 176 | } 177 | 178 | // Apply the height and width 179 | this.node.style.width = sizes.width; 180 | this.node.style.height = sizes.height; 181 | 182 | this.lightbox.node.style.height = sizes.playerheight + "px"; 183 | this.lightbox.node.style.width = sizes.playerwidth + "px"; 184 | this.lightbox.node.style.top = sizes.playeroffsettop + "px"; 185 | this.lightbox.node.style.left = sizes.playeroffsetleft + "px"; 186 | } 187 | 188 | /** 189 | * calculates the current lightbox size based on window width and height and on the players ratio 190 | * @param {float} ratio The players ratio 191 | * @return {object} Some sizes which can be used 192 | */ 193 | calculateLightboxSizes(ratio, maxwidth){ 194 | var sizes = {}; 195 | 196 | // Get window width && height 197 | sizes.width = window.clientWidth 198 | || document.documentElement.clientWidth 199 | || document.body.clientWidth 200 | || window.innerWidth; 201 | sizes.height = window.clientHeight 202 | || document.documentElement.clientHeight 203 | || document.body.clientHeight 204 | || window.innerHeight; 205 | 206 | // Window is wide enough 207 | if(sizes.height/sizes.width > ratio) 208 | { 209 | // Check if the lightbox should overscale, even if video is smaller 210 | if(typeof maxwidth !== 'undefined' && maxwidth < sizes.width * .90){ 211 | sizes.playerwidth = maxwidth; 212 | } 213 | // Else scale up as much as possible 214 | else{ 215 | sizes.playerwidth = sizes.width * .90; 216 | } 217 | sizes.playerheight = sizes.playerwidth * ratio; 218 | } 219 | else{ 220 | // Check if the lightbox should overscale, even if video is smaller 221 | if(typeof maxwidth !== 'undefined' && maxwidth < (sizes.height * .92)/ratio) 222 | { 223 | sizes.playerheight = maxwidth * ratio; 224 | } 225 | // Else scale up as much as possible 226 | else{ 227 | sizes.playerheight = sizes.height * .92; 228 | } 229 | sizes.playerwidth = sizes.playerheight / ratio; 230 | } 231 | sizes.playeroffsettop = ( sizes.height - sizes.playerheight ) / 2; 232 | sizes.playeroffsetleft = ( sizes.width - sizes.playerwidth ) / 2; 233 | 234 | return sizes; 235 | } 236 | 237 | /** 238 | * Closes the lightbox and removes the nodes from the DOM. 239 | * @return void 240 | */ 241 | close(){ 242 | window.afterglow.eventbus.dispatch(this.player.id, 'before-lightbox-close'); 243 | this.player.destroy(true); 244 | this.node.parentNode.removeChild(this.node); 245 | this.emit('close'); 246 | } 247 | 248 | /** 249 | * Returns the player 250 | * @return {Player object} 251 | */ 252 | getPlayer(){ 253 | if(this.player !== undefined){ 254 | return this.player.getPlayer(); 255 | } 256 | return undefined; 257 | } 258 | } 259 | 260 | export default Lightbox; -------------------------------------------------------------------------------- /src/js/afterglow/components/LightboxTrigger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * afterglow - An easy to integrate HTML5 video player with lightbox support. 3 | * @link http://afterglowplayer.com 4 | * @license MIT 5 | */ 6 | 'use strict'; 7 | 8 | import Lightbox from './Lightbox'; 9 | 10 | // For emitting and receiving events 11 | import Emitter from '../../../../vendor/Emitter/Emitter'; 12 | import DOMElement from '../lib/DOMElement'; 13 | 14 | class LightboxTrigger extends DOMElement { 15 | 16 | constructor(node){ 17 | super(node); 18 | this.init(); 19 | } 20 | 21 | /** 22 | * Initializes all atributes and prepares the trigger element for further interaction 23 | * @return {void} 24 | */ 25 | init(){ 26 | // Get the playerid 27 | this.playerid = this.node.getAttribute("href").replace('#',''); 28 | 29 | // Get the videoelement for this trigger 30 | let videoelement = document.querySelector('#'+this.playerid); 31 | this.videoelement = new DOMElement(videoelement); 32 | 33 | this.prepare(); 34 | 35 | Emitter(this); 36 | } 37 | 38 | /** 39 | * Prepares the video element to be used for the lightbox player. 40 | * @return {void} 41 | */ 42 | prepare(){ 43 | // Add major class 44 | this.videoelement.addClass("afterglow-lightboxplayer"); 45 | // Prepare the element 46 | this.videoelement.setAttribute("data-autoresize","fit"); 47 | 48 | this.bind('click', (e) => { 49 | e.preventDefault(); 50 | 51 | // Launch the lightbox 52 | this.trigger(); 53 | }); 54 | } 55 | 56 | /** 57 | * Creates all elements needed for the lightbox and launches the player. 58 | * @return {void} 59 | */ 60 | trigger(){ 61 | this.lightbox = new Lightbox(); 62 | 63 | var videoelement = this.videoelement.cloneNode(true); 64 | 65 | this.lightbox.passVideoElement(videoelement); 66 | 67 | this.emit('trigger'); 68 | window.afterglow.eventbus.dispatch(this.playerid, 'lightbox-launched'); 69 | 70 | this.lightbox.launch(); 71 | 72 | // Pass event to afterglow core 73 | // TODO: This must be tested. But it will break lightbox closing if it doesn't work, so that should be pretty obvious... 74 | this.lightbox.on('close', () => { 75 | window.afterglow.eventbus.dispatch(this.playerid, 'lightbox-closed'); 76 | this.emit('close'); 77 | }); 78 | } 79 | 80 | /** 81 | * Closes the lightbox if possible and removes the player from the trigger element so that it gets reinitiated on next trigger 82 | * @return {void} 83 | */ 84 | closeLightbox(){ 85 | if(this.lightbox != undefined){ 86 | this.lightbox.close(); 87 | this.deleteLightbox(); 88 | } 89 | } 90 | 91 | /** 92 | * Deletes the lightbox element if there is one. 93 | * @return {void} 94 | */ 95 | deleteLightbox(){ 96 | if(this.lightbox != undefined){ 97 | delete this.lightbox; 98 | } 99 | } 100 | 101 | /** 102 | * @return {object} Returns the player from the lightbox. 103 | */ 104 | getPlayer(){ 105 | if(this.lightbox !== undefined){ 106 | return this.lightbox.getPlayer(); 107 | } 108 | } 109 | 110 | } 111 | 112 | export default LightboxTrigger; -------------------------------------------------------------------------------- /src/js/afterglow/components/Player.js: -------------------------------------------------------------------------------- 1 | /** 2 | * afterglow - An easy to integrate HTML5 video player with lightbox support. 3 | * @link http://afterglowplayer.com 4 | * @license MIT 5 | */ 6 | 7 | 'use strict'; 8 | 9 | import Config from './Config'; 10 | import Util from '../lib/Util'; 11 | 12 | class Player { 13 | 14 | constructor(videoelement){ 15 | // Passing to setup for testability 16 | this.setup(videoelement); 17 | } 18 | 19 | /** 20 | * Sets the player up and prepares the video element 21 | * @param {DOMElement object} videoelement The videoelement which shall be transformed 22 | */ 23 | setup(videoelement){ 24 | this.videoelement = videoelement; 25 | this.id = videoelement.getAttribute('id'); 26 | 27 | // Prepare needed dependencies 28 | this.config = new Config(videoelement, this.getSkinName()); 29 | this.util = new Util(); 30 | 31 | // Prepare the element 32 | this.prepareVideoElement(); 33 | 34 | // Set an activity variable to be able to detect if the player can be deleted 35 | this.alive = true; 36 | } 37 | 38 | /** 39 | * Shortcut method which will apply some classes and parameters to the video element via some other methods 40 | * @return 41 | */ 42 | prepareVideoElement(){ 43 | this.applyDefaultClasses(); 44 | this.applyParameters(); 45 | 46 | if(this.util.isYoutubePlayer(this.videoelement)){ 47 | this.applyYoutubeClasses(); 48 | } 49 | 50 | else if(this.util.isVimeoPlayer(this.videoelement)){ 51 | this.applyVimeoClasses(); 52 | } 53 | } 54 | 55 | /** 56 | * Initializes the player and applies all needed stuff. 57 | * @param {function} _callback Callback function to be called when the player is ready 58 | * @return {void} 59 | */ 60 | init(_callback){ 61 | let videoelement = this.videoelement.node; 62 | let options = this.config.options; 63 | 64 | // initiate videojs and do some post initiation stuff 65 | var player = window.videojs(videoelement, options).ready(function(){ 66 | 67 | // Enable hotkeys 68 | this.hotkeys({ 69 | enableFullscreen: true, 70 | enableNumbers: false, 71 | enableVolumeScroll: false 72 | }); 73 | 74 | // Set initial volume if needed 75 | if(videoelement.getAttribute('data-volume') !== null){ 76 | var volume = parseFloat(videoelement.getAttribute('data-volume')); 77 | this.volume(volume); 78 | } 79 | 80 | // Add TopControBar 81 | this.addChild("TopControlBar"); 82 | 83 | this.on('play', () => { 84 | // Trigger afterglow play event 85 | window.afterglow.eventbus.dispatch(this.id(), 'play'); 86 | 87 | // Stop all other players if there are any on play 88 | for( let key in window.videojs.getPlayers() ) { 89 | if(window.videojs.getPlayers()[key] !== null && window.videojs.getPlayers()[key].id_ !== this.id_){ 90 | window.videojs.getPlayers()[key].pause(); 91 | } 92 | } 93 | 94 | // Remove youtube player class after 5 seconds if youtube player 95 | if(this.el_.classList.contains('vjs-youtube-headstart')){ 96 | let el = this.el_; 97 | setTimeout(function(){ 98 | el.classList.remove('vjs-youtube-headstart'); 99 | }, 3000) 100 | } 101 | }); 102 | 103 | // Trigger afterglow paused event 104 | this.on('pause', () => { 105 | window.afterglow.eventbus.dispatch(this.id(), 'paused'); 106 | }); 107 | 108 | // Trigger afterglow ended event 109 | this.on('ended', () => { 110 | window.afterglow.eventbus.dispatch(this.id(), 'ended'); 111 | }); 112 | 113 | // Trigger afterglow volume-changed event 114 | this.on('volumechange', () => { 115 | window.afterglow.eventbus.dispatch(this.id(), 'volume-changed'); 116 | }); 117 | 118 | // Trigger afterglow timeupdate event 119 | this.on('timeupdate', () => { 120 | window.afterglow.eventbus.dispatch(this.id(), 'timeupdate'); 121 | }); 122 | 123 | // Trigger afterglow fullscreen events 124 | this.on('fullscreenchange', () => { 125 | if(this.isFullscreen()){ 126 | window.afterglow.eventbus.dispatch(this.id(), 'fullscreen-entered'); 127 | } 128 | else{ 129 | window.afterglow.eventbus.dispatch(this.id(), 'fullscreen-left'); 130 | } 131 | window.afterglow.eventbus.dispatch(this.id(), 'fullscreen-changed'); 132 | }); 133 | 134 | // Launch the callback if there is one 135 | if(typeof _callback == "function"){ 136 | _callback(this); 137 | } 138 | 139 | // Trigger afterglow ready event 140 | window.afterglow.eventbus.dispatch(this.id(), 'ready'); 141 | 142 | this.on('autoplay', () => { 143 | // Trigger afterglow play event 144 | window.afterglow.eventbus.dispatch(this.id(), 'play'); 145 | }); 146 | }); 147 | this.videojs = player; 148 | } 149 | 150 | /** 151 | * Applies the default classes to the videoelement and removes sublime's class 152 | * @return {void} 153 | */ 154 | applyDefaultClasses(){ 155 | // Add some classes 156 | this.videoelement.addClass("video-js"); 157 | this.videoelement.addClass("afterglow"); 158 | 159 | let classNames = this.config.getSkinClass().split(" "); 160 | classNames.forEach((className) => { 161 | this.videoelement.addClass(className); 162 | }); 163 | 164 | // Remove sublime stuff 165 | this.videoelement.removeClass("sublime"); 166 | 167 | // Check for IE9 - IE11 168 | let ie = this.util.ie().actualVersion; 169 | if(ie >= 8 && ie <= 11){ // @see afterglow-lib.js 170 | this.videoelement.addClass('vjs-IE'); 171 | } 172 | } 173 | 174 | /** 175 | * Applies basic parameters to the videoelement to make it well usable for video.js 176 | * @return {void} 177 | */ 178 | applyParameters(){ 179 | // Make lightboxplayer not overscale 180 | if(this.videoelement.getAttribute("data-overscale") == "false"){ 181 | this.videoelement.setAttribute("data-maxwidth",this.videoelement.getAttribute("width")); 182 | } 183 | 184 | // Apply some responsive stylings 185 | if(this.videoelement.getAttribute("data-autoresize") != 'none' && this.videoelement.getAttribute("data-autoresize") != 'false'){ 186 | this.videoelement.addClass("vjs-responsive"); 187 | let ratio = this.calculateRatio(); 188 | this.videoelement.node.style.paddingTop = (ratio * 100)+"%"; 189 | this.videoelement.removeAttribute("height"); 190 | this.videoelement.removeAttribute("width"); 191 | this.videoelement.setAttribute("data-ratio",ratio); 192 | } 193 | } 194 | 195 | /** 196 | * Applies all needed classes to the videoelement in order to provide proper youtube playback 197 | * @return {void} 198 | */ 199 | applyYoutubeClasses(){ 200 | this.videoelement.addClass("vjs-youtube"); 201 | this.videoelement.addClass("vjs-youtube-headstart"); 202 | 203 | // Check for native playback 204 | if(document.querySelector('video').controls){ 205 | this.videoelement.addClass("vjs-using-native-controls"); 206 | } 207 | // Add iOS class for iOS. 208 | if(/iPad|iPhone|iPod|iOS/.test(navigator.platform)){ 209 | this.videoelement.addClass("vjs-iOS"); 210 | } 211 | 212 | // Check for IE9 - IE11 213 | let ie = this.util.ie().actualVersion; 214 | if(ie >= 8 && ie <= 11){ // @see afterglow-lib.js 215 | this.videoelement.addClass("vjs-using-native-controls"); 216 | } 217 | } 218 | 219 | /** 220 | * Applies all needed classes to the videoelement in order to provide proper vimeo playback 221 | * @return {void} 222 | */ 223 | applyVimeoClasses(){ 224 | this.videoelement.addClass("vjs-vimeo"); 225 | } 226 | 227 | /** 228 | * Calculates the players ratio based on the given value or on width/height 229 | * @return {float} 230 | */ 231 | calculateRatio(){ 232 | if(this.videoelement.getAttribute("data-ratio")){ 233 | var ratio = this.videoelement.getAttribute("data-ratio"); 234 | } 235 | else if(!this.videoelement.getAttribute("height") || !this.videoelement.getAttribute("width")) 236 | { 237 | console.error("Please provide witdh and height for your video element."); 238 | return 0; 239 | } 240 | else{ 241 | var ratio = this.videoelement.getAttribute("height") / this.videoelement.getAttribute("width"); 242 | } 243 | return parseFloat(ratio); 244 | } 245 | 246 | /** 247 | * Gets the current player's skin name for further use in css variables and so on. 248 | * @return {string} 249 | */ 250 | getSkinName(){ 251 | if(this.videoelement.getAttribute('data-skin')){ 252 | return this.videoelement.getAttribute('data-skin'); 253 | } 254 | return 'afterglow'; 255 | } 256 | 257 | /** 258 | * Destroys the player instance and disposes it. 259 | * @return {void} 260 | */ 261 | destroy(){ 262 | if(!this.videojs.paused()){ 263 | this.videojs.pause(); 264 | } 265 | if(this.videojs.isFullscreen()){ 266 | this.videojs.exitFullscreen(); 267 | } 268 | this.videojs.dispose(); 269 | this.alive = false; 270 | } 271 | 272 | /** 273 | * Getter for the player 274 | */ 275 | getPlayer(){ 276 | return this.videojs; 277 | } 278 | } 279 | 280 | export default Player; -------------------------------------------------------------------------------- /src/js/afterglow/lib/DOMElement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class DOMElement{ 4 | 5 | constructor(node){ 6 | this.node = node; 7 | } 8 | 9 | /** 10 | * Adds a given class to the DOM node if the node doesn't have it. 11 | * @param {string} className 12 | * @return {object} this - for method chaining 13 | */ 14 | addClass(className){ 15 | if(this.node.classList) { 16 | this.node.classList.add(className); 17 | } else if (!this.hasClass(className)) { 18 | this.node.className += ' ' + className; 19 | } 20 | return this; 21 | } 22 | 23 | /** 24 | * Removes a given class from the DOM node if the node doesn't have it. 25 | * @param {string} className 26 | * @return {object} this - for method chaining 27 | */ 28 | removeClass(className){ 29 | if (this.node.classList) { 30 | this.node.classList.remove(className); 31 | } else { 32 | var classes = this.node.className.split(" "); 33 | classes.splice(classes.indexOf(className), 1); 34 | this.node.className = classes.join(" "); 35 | } 36 | return this; 37 | } 38 | 39 | /** 40 | * Will detect if the node does have the given className 41 | * @param {string} className 42 | * @return {Boolean} 43 | */ 44 | hasClass(className){ 45 | if (this.node.classList) { 46 | return this.node.classList.contains(className); 47 | } else { 48 | return (-1 < this.node.className.indexOf(className)); 49 | } 50 | } 51 | 52 | /** 53 | * IE8 compliant way of handling event bindings with the possibility to remove them lateron, with support for multiple events at once 54 | * @param {string} eventType The events to react to 55 | * @param {function} handler The function to execute 56 | * @return {object} this - for method chaining 57 | */ 58 | bind(eventType,handler) { 59 | var evts = eventType.split(' '); 60 | for (var i=0, iLen=evts.length; i 0 && !/x64|x32/ig.test(window.navigator.userAgent)){ 82 | ret = { 83 | isTheBrowser: true, 84 | actualVersion: "11" 85 | }; 86 | } 87 | } 88 | return ret; 89 | } 90 | 91 | /** 92 | * Checks wether or not the currently used device is a mobile one 93 | * @return {boolean} 94 | */ 95 | isMobile(){ 96 | var Android = () => { return navigator.userAgent.match(/Android/i); }; 97 | var BlackBerry = () => { return navigator.userAgent.match(/BlackBerry/i); }; 98 | var iOS = () => { return navigator.userAgent.match(/iPhone|iPad|iPod/i); }; 99 | var Opera = () => { return navigator.userAgent.match(/Opera Mini/i); }; 100 | var Windows = () => { return navigator.userAgent.match(/IEMobile/i); }; 101 | 102 | return (Android() || BlackBerry() || iOS() || Opera() || Windows()) ? true : false; 103 | } 104 | 105 | /** 106 | * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 107 | * @param obj1 108 | * @param obj2 109 | * @returns obj3 a new object based on obj1 and obj2 110 | */ 111 | merge_objects(obj1,obj2){ 112 | var obj3 = {}; 113 | for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; } 114 | for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; } 115 | return obj3; 116 | } 117 | 118 | /** 119 | * Adds an event to some node object 120 | * @param {node} object The DOM node that should be bound 121 | * @param {string} type The event to bind 122 | * @param {Function} callback 123 | */ 124 | addEventListener(object, type, callback) { 125 | if (object == null || typeof(object) == 'undefined') return; 126 | if (object.addEventListener) { 127 | object.addEventListener(type, callback, false); 128 | } else if (object.attachEvent) { 129 | object.attachEvent("on" + type, callback); 130 | } else { 131 | object["on"+type] = callback; 132 | } 133 | }; 134 | 135 | } 136 | 137 | export default Util; -------------------------------------------------------------------------------- /src/js/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * afterglow - An easy to integrate HTML5 video player with lightbox support. 3 | * @link http://afterglowplayer.com 4 | * @license MIT 5 | */ 6 | 7 | import Afterglow from './afterglow/Afterglow'; 8 | 9 | window.afterglow = new Afterglow(); 10 | 11 | // Initiate afterglow when the DOM is ready. This is not IE8 compatible! 12 | document.addEventListener("DOMContentLoaded", function() { 13 | window.afterglow.init(); 14 | }); -------------------------------------------------------------------------------- /src/js/vjs-components/LightboxCloseButton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const VjsLBButtonClose = videojs.getComponent('Button'); 4 | 5 | /** 6 | * Button to close the lightbox 7 | * 8 | * @extends Button 9 | * @class LightboxCloseButton 10 | */ 11 | class LightboxCloseButton extends VjsLBButtonClose { 12 | 13 | constructor(player, options) { 14 | super(player, options); 15 | this.on('click', this.closeClick); 16 | this.on('tap', this.closeClick); 17 | } 18 | 19 | buildCSSClass(){ 20 | return 'vjs-lightbox-close-button vjs-button vjs-control'; 21 | } 22 | 23 | /** 24 | * This will close afterglow's lightbox and remove the player from the DOM 25 | * @return {void} 26 | */ 27 | closeClick(){ 28 | afterglow.closeLightbox(); 29 | } 30 | } 31 | 32 | 33 | (function(){ 34 | 35 | videojs.registerComponent('LightboxCloseButton', LightboxCloseButton); 36 | 37 | })(); -------------------------------------------------------------------------------- /src/js/vjs-components/ResolutionSwitchingButton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const VjsButtonResBBase = videojs.getComponent('Button'); 4 | 5 | /** 6 | * Button to switch resolutions based on the data-quality attribute. 7 | * 8 | * @extends Button 9 | * @class LightboxCloseButton 10 | */ 11 | class ResolutionSwitchingButton extends VjsButtonResBBase { 12 | 13 | constructor(player, options) { 14 | super(player, options); 15 | this.init(); 16 | } 17 | 18 | buildCSSClass(){ 19 | return `vjs-ag-res-button ${super.buildCSSClass()}`; 20 | } 21 | 22 | /** 23 | * Initiates the button and its functionality 24 | * @return {void} 25 | */ 26 | init(){ 27 | this.prepareSources(); 28 | this.setResolutionsNeededFromPlayer(); 29 | if(this.resolutionsNeeded){ 30 | this.switchSource(this.sources[0]); 31 | this.setCurrentResFromPlayer(); 32 | 33 | // Bind events 34 | this.on('click',this.switchResolution); 35 | this.on('tap',this.switchResolution); 36 | } 37 | this.updateButton(); 38 | 39 | var this_ = this; 40 | this.player_.on('ready', function(){ 41 | this_.updateButton; 42 | }); 43 | this.player_.on('play', function(){ 44 | this_.updateButton; 45 | }); 46 | } 47 | 48 | /** 49 | * Prepares the sources that are needed for the button functionality 50 | * @return {void} 51 | */ 52 | prepareSources(){ 53 | this.sources = this.getAbsoluteSources(); 54 | this.sourcesByType = videojs.reduce(this.sources, function(init, val, i){ 55 | (init[val.type] = init[val.type] || []).push(val); 56 | return init; 57 | }, {}, this.player_); 58 | this.typeAndTech = this.selectTypeAndTech(this.sources); 59 | } 60 | 61 | /** 62 | * Sets the currentRes based on the currently played source 63 | */ 64 | setCurrentResFromPlayer(){ 65 | var currentSrc = this.player_.src(); 66 | var allSources = this.sources; 67 | for (var i = allSources.length - 1; i >= 0; i--) { 68 | if(allSources[i]['src'] == currentSrc){ 69 | if(allSources[i]['data-quality'] !== 'hd'){ 70 | this.currentRes = 'sd'; 71 | } 72 | else{ 73 | this.currentRes = 'hd'; 74 | } 75 | } 76 | }; 77 | } 78 | 79 | /** 80 | * Checks if the plugin is needed for that player 81 | */ 82 | setResolutionsNeededFromPlayer(){ 83 | // Fallback 84 | this.resolutionsNeeded = false; 85 | // Real determination 86 | if(typeof this.typeAndTech == 'object'){ 87 | var type = this.typeAndTech.type; 88 | if(this.sourcesByType[type] !== undefined 89 | && this.sourcesByType[type].length > 1){ 90 | for (var i = this.sourcesByType[type].length - 1; i >= 0; i--) { 91 | if(this.sourcesByType[type][i]['data-quality'] !== undefined 92 | || this.sourcesByType[type][i]['data-quality'] != this.currentRes){ 93 | this.resolutionsNeeded = true; 94 | return; 95 | } 96 | }; 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * Removes all sources without actually disposing the player 103 | * @return {void} 104 | */ 105 | removeSources(){ 106 | var videoEl = this.player_.el_.getElementsByTagName("video")[0]; 107 | 108 | if (this.player_.techName_ !== "Html5" || !videoEl) return; 109 | 110 | var srcs = videoEl.getElementsByTagName("source"); 111 | for(var i=0;i= 0; i--) { 122 | var src = sources[i].src; 123 | if(src !== undefined && src !== ''){ 124 | // Handle absolute URLs (with protocol-relative prefix) 125 | // Example: //domain.com/file.png 126 | if (src.search(/^\/\//) != -1) { 127 | src = protocol + src; 128 | } 129 | 130 | // Handle absolute URLs (with explicit origin) 131 | // Example: http://domain.com/file.png 132 | else if (src.search(/:\/\//) != -1) { 133 | continue; 134 | } 135 | 136 | // Handle absolute URLs (without explicit origin) 137 | // Example: /file.png 138 | else if (src.search(/^\//) != -1) { 139 | src = origin + src; 140 | } 141 | 142 | // Handle relative URLs 143 | // Example: file.png 144 | else{ 145 | src = base + src; 146 | } 147 | sources[i].src = src; 148 | } 149 | }; 150 | return sources; 151 | } 152 | 153 | /** 154 | * Gets the source that should be launched on the next resolution change 155 | * @return {object} The source object which should be played 156 | */ 157 | getSourceForResolutionChange(){ 158 | var type = this.typeAndTech.type; 159 | var availableSources = this.sourcesByType[type]; 160 | for (var i = availableSources.length - 1; i >= 0; i--) { 161 | if(this.currentRes == 'hd'){ 162 | if(availableSources[i]['data-quality'] == undefined 163 | || availableSources[i]['data-quality'] !== 'hd') 164 | return availableSources[i]; 165 | } 166 | else{ 167 | if(availableSources[i]['data-quality'] == 'hd') 168 | return availableSources[i]; 169 | } 170 | }; 171 | // Fallback if all other options failed... 172 | return availableSources[0]; 173 | } 174 | 175 | /** 176 | * Changes the currently played source to another solution if possible 177 | * @return {[type]} [description] 178 | */ 179 | switchResolution(){ 180 | var sourceToPlay = this.getSourceForResolutionChange(); 181 | this.switchSource(sourceToPlay); 182 | } 183 | 184 | /** 185 | * Stops the currently playing stream without disposing the player 186 | * @return {[type]} [description] 187 | */ 188 | stopStream(){ 189 | switch(this.player_.techName_){ 190 | case "Flash": 191 | this.player_.tech_.el_.vjs_stop(); 192 | break; 193 | } 194 | } 195 | 196 | /** 197 | * Selectes sources by type 198 | */ 199 | selectSource(sources){ 200 | this.removeSources(); 201 | 202 | var sourcesByType = this.sourcesByType; 203 | var typeAndTech = this.selectTypeAndTech(sources); 204 | 205 | if (!typeAndTech) return false; 206 | 207 | // even though we choose the best resolution for the user here, we 208 | // should remember the resolutions so that we can potentially 209 | // change resolution later 210 | this.options_['sourceResolutions'] = sourcesByType[typeAndTech.type]; 211 | 212 | return this.selectResolution(this.options_['sourceResolutions']); 213 | } 214 | 215 | /** 216 | * Returns the media type and tech to use 217 | * @return {object} 218 | */ 219 | selectTypeAndTech(sources) { 220 | var techName; 221 | var tech; 222 | 223 | for (var i=0,j=this.player_.options_['techOrder'];i maxRes ? maxRes : preferredRes; 264 | 265 | return typeSources[actualRes]; 266 | } 267 | 268 | /** takes a source and switches the player's stream to it on the fly 269 | * @param {Object} singular source: 270 | * { 271 | * "data-default": "true", 272 | * "data-res": "SD", 273 | * "type": "video/mp4", 274 | * "src": "http://some_video_url_sd" 275 | * } 276 | */ 277 | switchSource(new_source){ 278 | // has the exact same source been chosen? 279 | if (this.player_.cache_.src === new_source.src){ 280 | this.player_.trigger('resolutionchange'); 281 | return this.player_; // basically a no-op 282 | } 283 | 284 | // remember our position and playback state 285 | var curTime = this.player_.currentTime(); 286 | var remainPaused = this.player_.paused(); 287 | 288 | // pause playback 289 | this.player_.pause(); 290 | 291 | // attempts to stop the download of the existing video 292 | this.stopStream(); 293 | 294 | // HTML5 tends to not recover from reloading the tech but it can 295 | // generally handle changing src. Flash generally cannot handle 296 | // changing src but can reload its tech. 297 | if (this.player_.techName_ === "Html5"){ 298 | this.player_.src(new_source.src); 299 | } else { 300 | this.player_.loadTech(this.player_.techName_, {src: new_source.src}); 301 | } 302 | 303 | var _this = this; 304 | 305 | // when the technology is re-started, kick off the new stream 306 | this.player_.ready(function() { 307 | this.player_.one('loadeddata', videojs.bind(this.player_, function() { 308 | this.player_.currentTime(curTime); 309 | })); 310 | 311 | this.player_.trigger('resolutionchange'); 312 | 313 | if (!remainPaused) { 314 | this.player_.load(); 315 | this.player_.play(); 316 | } 317 | 318 | // remember this selection 319 | localStorage.setItem('videojs_preferred_res', parseInt(new_source.index, 10)); 320 | 321 | _this.setCurrentResFromPlayer(); 322 | _this.updateButton(); 323 | }); 324 | } 325 | 326 | /** 327 | * Updates the button display the currently active quality. 328 | */ 329 | updateButton(){ 330 | var buttonEl = this.prepareButtonElement(this.el_); 331 | 332 | if(!this.resolutionsNeeded){ 333 | buttonEl.addClass("vjs-hidden"); 334 | } 335 | else{ 336 | buttonEl.removeClass("vjs-hidden"); 337 | } 338 | 339 | if(this.currentRes == 'hd'){ 340 | buttonEl.addClass("vjs-ag-res-hd"); 341 | } 342 | else{ 343 | buttonEl.removeClass("vjs-ag-res-hd"); 344 | } 345 | 346 | // Get rid of the focus when having clicked the button 347 | this.el_.blur(); 348 | } 349 | 350 | prepareButtonElement(buttonEl){ 351 | if(typeof buttonEl.addClass !== 'function'){ 352 | buttonEl.addClass = function(className){ 353 | if(this.classList) { 354 | this.classList.add(className); 355 | } else if (-1 == this.className.indexOf(className)) { 356 | var classes = this.className.split(" "); 357 | classes.push(className); 358 | this.className = classes.join(" "); 359 | } 360 | return this; 361 | } 362 | } 363 | if(typeof buttonEl.removeClass !== 'function'){ 364 | buttonEl.removeClass = function(className){ 365 | if (this.classList) { 366 | this.classList.remove(className); 367 | } else { 368 | var classes = this.className.split(" "); 369 | classes.splice(classes.indexOf(className), 1); 370 | this.className = classes.join(" "); 371 | } 372 | return this; 373 | } 374 | } 375 | return buttonEl; 376 | } 377 | } 378 | 379 | /** 380 | * 'reduce' utility method 381 | * @param {Array} array to iterate over 382 | * @param {Function} iterator function for collector 383 | * @param {Array|Object|Number|String} initial collector 384 | * @return collector 385 | */ 386 | videojs.reduce = function(arr, fn, init, n) { 387 | if (!arr || arr.length === 0) { return; } 388 | for (var i=0,j=arr.length; i { 20 | // Initiate the DOM 21 | jsdom(); 22 | 23 | var afterglow, 24 | $; 25 | 26 | before(() => { 27 | // Mocking the player methods that are called 28 | sinon.stub(Player.prototype, 'init', () => {} ); 29 | sinon.stub(Player.prototype, 'setup', () => {} ); 30 | 31 | sinon.stub(LightboxTrigger.prototype, 'init', () => {} ); 32 | }); 33 | 34 | beforeEach(() => { 35 | $ = require('jquery'); 36 | 37 | afterglow = new Afterglow(); 38 | }); 39 | 40 | after(() => { 41 | // Restore stubbed methods 42 | Player.prototype.init.restore(); 43 | Player.prototype.setup.restore(); 44 | LightboxTrigger.prototype.init.restore(); 45 | }); 46 | 47 | describe("Bootup", () => { 48 | 49 | /** 50 | * GENERAL INITIATION TESTS 51 | */ 52 | 53 | it('initiates the player container properly', () => { 54 | afterglow.players.should.be.an('array'); 55 | afterglow.players.should.be.empty; 56 | }); 57 | 58 | it('initiates the lightbox trigger container properly', () => { 59 | afterglow.lightboxtriggers.should.be.an('array'); 60 | afterglow.lightboxtriggers.should.be.empty; 61 | }); 62 | 63 | it('calls video.js configuration on init', () =>{ 64 | sinon.spy(afterglow, "configureVideoJS"); 65 | afterglow.init(); 66 | assert(afterglow.configureVideoJS.calledOnce); 67 | }); 68 | 69 | it('calls video initiation on init', () =>{ 70 | sinon.spy(afterglow, "initVideoElements"); 71 | afterglow.init(); 72 | assert(afterglow.initVideoElements.calledOnce); 73 | }); 74 | 75 | it('calls lightbox preparation on init', () =>{ 76 | sinon.spy(afterglow, "prepareLightboxVideos"); 77 | afterglow.init(); 78 | assert(afterglow.prepareLightboxVideos.calledOnce); 79 | }); 80 | 81 | it('configures video.js properly', () =>{ 82 | afterglow.configureVideoJS(); 83 | window.HELP_IMPROVE_VIDEOJS.should.be.false; 84 | }); 85 | }); 86 | 87 | describe("Video element initiation", () => { 88 | 89 | /** 90 | * VIDEO ELEMENT SETUP TESTS 91 | */ 92 | 93 | it('detects video elements properly, including SublimeVideo markup and creates players of them.', () =>{ 94 | // Mock the DOM 95 | document.body.innerHTML = ''; 96 | 97 | // Run the tests 98 | let res = afterglow.initVideoElements(); 99 | 100 | sinon.assert.calledTwice(Player.prototype.init); 101 | }); 102 | 103 | it('adds launched video players to the players object', () => { 104 | // Mock the DOM 105 | document.body.innerHTML = ''; 106 | // 0 before initialization 107 | afterglow.players.should.be.length(0); 108 | 109 | let res = afterglow.initVideoElements(); 110 | 111 | // Holding two of them afterwards 112 | afterglow.players.should.be.length(2); 113 | }); 114 | }); 115 | 116 | describe("Lightbox element initiation", () => { 117 | 118 | /** 119 | * VIDEO ELEMENT SETUP TESTS 120 | */ 121 | 122 | it('detects lightbox elements properly, including SublimeVideo markup and prepares the triggers.', () =>{ 123 | // Mock the DOM 124 | document.body.innerHTML = ''; 125 | 126 | sinon.stub(afterglow, 'bindLightboxTriggerEvents', () => {} ); 127 | 128 | // Run the tests 129 | let res = afterglow.prepareLightboxVideos(); 130 | 131 | sinon.assert.calledTwice(LightboxTrigger.prototype.init); 132 | sinon.assert.calledTwice(afterglow.bindLightboxTriggerEvents); 133 | }); 134 | 135 | it('passes initialized lightbox triggers to the trigger container',() => {// Mock the DOM 136 | document.body.innerHTML = ''; 137 | 138 | sinon.stub(afterglow, 'bindLightboxTriggerEvents', () => {} ); 139 | 140 | afterglow.lightboxtriggers.should.be.length(0); 141 | 142 | // Run the tests 143 | let res = afterglow.prepareLightboxVideos(); 144 | 145 | afterglow.lightboxtriggers.should.be.length(2); 146 | }); 147 | 148 | it('binds lightbox trigger events properly', () => { 149 | let Trigger = { 150 | on : () => {} 151 | }; 152 | 153 | sinon.spy(Trigger,'on'); 154 | 155 | afterglow.bindLightboxTriggerEvents(Trigger); 156 | assert(Trigger.on.calledTwice); 157 | }); 158 | }); 159 | 160 | describe("API methods", () => { 161 | it('should return the players videojs instance when getting by id',() => { 162 | afterglow.players = [ 163 | { 164 | id : 'testid', 165 | getPlayer: ()=>{ return 'test1' } 166 | } 167 | ]; 168 | 169 | afterglow.lightboxtriggers = [ 170 | { 171 | playerid : 'testid2', 172 | getPlayer: () => { 173 | return 'test2'; 174 | } 175 | } 176 | ]; 177 | 178 | let regularPlayer = afterglow.getPlayer('testid'); 179 | let lightboxPlayer = afterglow.getPlayer('testid2'); 180 | 181 | regularPlayer.should.equal('test1'); 182 | lightboxPlayer.should.equal('test2'); 183 | }); 184 | 185 | it('should return false when the id was not found', () => { 186 | afterglow.players = [ 187 | { 188 | id : 'testid' 189 | } 190 | ]; 191 | let regularPlayer = afterglow.getPlayer('nonexistingtestid'); 192 | let lightboxPlayer = afterglow.getPlayer('nonexistingtestid2'); 193 | regularPlayer.should.be.false; 194 | lightboxPlayer.should.be.false; 195 | }); 196 | 197 | it('should delete players from the player container when deleting by id',() => { 198 | let destroyTest = { 199 | alert : () => {} 200 | } 201 | 202 | afterglow.players = [ 203 | { 204 | id : 'testid', 205 | destroy : () => { 206 | destroyTest.alert(); 207 | } 208 | } 209 | ]; 210 | sinon.spy(destroyTest, 'alert'); 211 | afterglow.players.should.be.length(1); 212 | 213 | afterglow.destroyPlayer('testid'); 214 | 215 | assert(destroyTest.alert.calledOnce); 216 | afterglow.players.should.be.length(0); 217 | }); 218 | 219 | it('should return false if there was not lightbox to destroy', () => { 220 | afterglow.players = [ 221 | { 222 | id : 'testid' 223 | } 224 | ]; 225 | 226 | afterglow.players.should.be.length(1); 227 | 228 | let res = afterglow.destroyPlayer('nonexistingid'); 229 | res.should.be.false; 230 | }); 231 | 232 | it('should close lightbox for lightbox players when deleting by id',() => { 233 | afterglow.lightboxtriggers = [ 234 | { 235 | playerid : 'testid', 236 | } 237 | ]; 238 | sinon.stub(afterglow, 'closeLightbox', () => {} ); 239 | 240 | afterglow.destroyPlayer('testid'); 241 | 242 | sinon.assert.calledOnce(afterglow.closeLightbox); 243 | }); 244 | 245 | it('should pass play method for regular players',() => { 246 | var testobject = { 247 | play: () => { 248 | return 'success'; 249 | } 250 | }; 251 | 252 | afterglow.players = [ 253 | { 254 | id : 'testid', 255 | getPlayer: () => { 256 | return testobject; 257 | } 258 | } 259 | ]; 260 | 261 | afterglow.lightboxtriggers = []; 262 | 263 | sinon.spy(testobject, 'play'); 264 | 265 | afterglow.play('testid'); 266 | 267 | sinon.assert.calledOnce(testobject.play); 268 | }); 269 | 270 | it('should trigger lightboxes',() => { 271 | var testTrigger = { 272 | playerid : 'testid', 273 | trigger: () => { 274 | return 'triggersuccess'; 275 | } 276 | } 277 | 278 | sinon.spy(testTrigger, 'trigger'); 279 | 280 | afterglow.lightboxtriggers = [ testTrigger ]; 281 | afterglow.players = []; 282 | 283 | afterglow.play('testid'); 284 | 285 | sinon.assert.calledOnce(testTrigger.trigger); 286 | }); 287 | 288 | it('should properly close all lightboxes when closing is triggered', () => { 289 | let closeTest = { 290 | alert : () => {} 291 | } 292 | 293 | afterglow.lightboxtriggers = [ 294 | { 295 | closeLightbox : () => { 296 | closeTest.alert(); 297 | } 298 | }, 299 | { 300 | closeLightbox : () => { 301 | closeTest.alert(); 302 | } 303 | } 304 | ]; 305 | sinon.spy(closeTest, 'alert'); 306 | sinon.stub(afterglow, 'consolidatePlayers', () => {} ); 307 | 308 | afterglow.closeLightbox(); 309 | 310 | assert(closeTest.alert.calledTwice); 311 | sinon.assert.calledOnce(afterglow.consolidatePlayers); 312 | }); 313 | }); 314 | 315 | describe('Consolidation',() => { 316 | it('should remove players that have been destroyed', () => { 317 | afterglow.players = [ 318 | { 319 | alive: false 320 | },{ 321 | alive: true 322 | },{ 323 | alive: true 324 | },{ 325 | }, 326 | ]; 327 | afterglow.players.should.be.length(4); 328 | afterglow.consolidatePlayers(); 329 | afterglow.players.should.be.length(2); 330 | }); 331 | it('should reindex the player container after removing destroyed players', () => { 332 | afterglow.players = [ 333 | { 334 | alive: false 335 | },{ 336 | alive: true 337 | },{ 338 | alive: true 339 | },{ 340 | }, 341 | ]; 342 | afterglow.players.should.have.all.keys(["0","1","2","3"]); 343 | afterglow.consolidatePlayers(); 344 | afterglow.players.should.have.all.keys(["0","1"]); 345 | 346 | }); 347 | }); 348 | }); -------------------------------------------------------------------------------- /test/test.2.config.js: -------------------------------------------------------------------------------- 1 | import Afterglow from '../src/js/afterglow/Afterglow'; 2 | import Player from '../src/js/afterglow/components/Player'; 3 | import Lightbox from '../src/js/afterglow/components/Lightbox'; 4 | import LightboxTrigger from '../src/js/afterglow/components/LightboxTrigger'; 5 | import Config from '../src/js/afterglow/components/Config'; 6 | import Util from '../src/js/afterglow/lib/Util'; 7 | 8 | var chai = require('chai'); 9 | var sinon = require("sinon"); 10 | var sinonChai = require("sinon-chai"); 11 | var jsdom = require('mocha-jsdom'); 12 | 13 | chai.use(sinonChai); 14 | chai.should(); 15 | 16 | var assert = chai.assert; 17 | var expect = chai.expect; 18 | 19 | describe("Afterglow Config", () => { 20 | // Initiate the DOM 21 | jsdom(); 22 | 23 | var a_config, 24 | $, 25 | videoelement, 26 | sandbox; 27 | 28 | beforeEach(() => { 29 | $ = require('jquery'); 30 | 31 | // stub some console methods 32 | sinon.stub(window.console, "error"); 33 | 34 | document.body.innerHTML = ''; 35 | videoelement = document.querySelector('video'); 36 | }); 37 | 38 | afterEach(() => { 39 | // restore the environment as it was before 40 | window.console.error.restore(); 41 | }); 42 | 43 | describe('Constructor', () => { 44 | beforeEach(() => { 45 | sinon.stub(Config.prototype, 'init', () => {}); 46 | }); 47 | 48 | afterEach(() => { 49 | Config.prototype.init.restore(); 50 | }); 51 | 52 | it('should call init properly', () => { 53 | a_config = new Config(); 54 | sinon.assert.calledOnce(Config.prototype.init); 55 | }); 56 | 57 | it('should pass the videoelement to init', () => { 58 | a_config = new Config('false'); 59 | expect(Config.prototype.init).to.have.been.calledWith('false', 'afterglow'); 60 | }); 61 | 62 | it('should pass the skin to init', () => { 63 | a_config = new Config('false','false2'); 64 | expect(Config.prototype.init).to.have.been.calledWith('false','false2'); 65 | }); 66 | }); 67 | 68 | describe('init', () => { 69 | beforeEach(() => { 70 | sinon.stub(Config.prototype, 'setDefaultOptions', () => {}); 71 | sinon.stub(Config.prototype, 'setYoutubeOptions', () => {}); 72 | sinon.stub(Config.prototype, 'setVimeoOptions', () => {}); 73 | sinon.stub(Config.prototype, 'setSkinControls', () => {}); 74 | }); 75 | 76 | afterEach(() => { 77 | Config.prototype.setDefaultOptions.restore(); 78 | Config.prototype.setYoutubeOptions.restore(); 79 | Config.prototype.setVimeoOptions.restore(); 80 | Config.prototype.setSkinControls.restore(); 81 | }); 82 | 83 | it('logs an error if no videoelement is passed', () => { 84 | a_config = new Config(); 85 | sinon.assert.calledOnce(console.error); 86 | }); 87 | 88 | it('should set the videoelement correctly', () => { 89 | a_config = new Config(videoelement); 90 | a_config.videoelement.should.equal(videoelement); 91 | }); 92 | 93 | it('should set the skin name correctly', () => { 94 | a_config = new Config(videoelement); 95 | a_config.skin.should.equal('afterglow'); 96 | a_config = new Config(videoelement,'someskin'); 97 | a_config.skin.should.equal('someskin'); 98 | }); 99 | 100 | it('should initialize the options container correctly', () => { 101 | a_config = new Config(videoelement); 102 | a_config.options.should.be.an('object'); 103 | a_config.options.should.be.empty; 104 | }); 105 | 106 | it('should set the default options', () => { 107 | a_config = new Config(videoelement); 108 | sinon.assert.calledOnce(a_config.setDefaultOptions); 109 | }); 110 | 111 | it('should set the skin controls', () => { 112 | a_config = new Config(videoelement); 113 | sinon.assert.calledOnce(a_config.setSkinControls); 114 | }); 115 | 116 | it('should set the youtube options if needed', () => { 117 | sinon.stub(Util.prototype, 'isYoutubePlayer', () => { return true; }); 118 | a_config = new Config(videoelement); 119 | sinon.assert.calledOnce(a_config.setYoutubeOptions); 120 | Util.prototype.isYoutubePlayer.restore(); 121 | }); 122 | 123 | it('should set the vimeo options if needed', () => { 124 | sinon.stub(Util.prototype, 'isVimeoPlayer', () => { return true; }); 125 | a_config = new Config(videoelement); 126 | sinon.assert.calledOnce(a_config.setVimeoOptions); 127 | Util.prototype.isVimeoPlayer.restore(); 128 | }); 129 | }); 130 | 131 | describe('Option defaults', () => { 132 | beforeEach(() => { 133 | sinon.stub(Config.prototype, 'getPlayerAttributeFromVideoElement', () => { return "test1" }); 134 | 135 | sinon.stub(Config.prototype, 'init', () => {}); 136 | a_config = new Config(videoelement); 137 | a_config.videoelement = videoelement; 138 | a_config.options = {}; 139 | a_config.setDefaultOptions(); 140 | }); 141 | 142 | afterEach(() => { 143 | Config.prototype.getPlayerAttributeFromVideoElement.restore(); 144 | Config.prototype.init.restore(); 145 | }); 146 | 147 | it('sets the options variable properly', () => { 148 | a_config.options.should.be.an('object'); 149 | expect(a_config.options).to.contain.all.keys('autoplay','techOrder','poster','preload','controls'); 150 | }); 151 | 152 | it('should call getPreloadValue() once', () => { 153 | sinon.assert.calledThrice(a_config.getPlayerAttributeFromVideoElement); 154 | a_config.options.preload.should.equal("test1"); 155 | }); 156 | }); 157 | 158 | describe('Attribute getter', () => { 159 | beforeEach(() => { 160 | sinon.stub(Config.prototype, 'init', () => {}); 161 | }); 162 | 163 | afterEach(() => { 164 | Config.prototype.init.restore(); 165 | }); 166 | 167 | it('should return false as default fallback value', () => { 168 | a_config = new Config(videoelement); 169 | a_config.videoelement = videoelement; 170 | a_config.getPlayerAttributeFromVideoElement('somenonexistingattribute').should.be.false; 171 | }); 172 | 173 | it('should return the given fallback value if given', () => { 174 | a_config = new Config(videoelement); 175 | a_config.videoelement = videoelement; 176 | a_config.getPlayerAttributeFromVideoElement('somenonexistingattribute','fallback').should.equal('fallback'); 177 | }); 178 | 179 | it('should return the correct value for data-[attributename]', () => { 180 | videoelement.setAttribute('data-someattr','sometest'); 181 | a_config = new Config(videoelement); 182 | a_config.videoelement = videoelement; 183 | a_config.getPlayerAttributeFromVideoElement('someattr').should.equal('sometest'); 184 | }); 185 | 186 | it('should return the correct value for [attributename]', () => { 187 | videoelement.setAttribute('someattr','sometest'); 188 | a_config = new Config(videoelement); 189 | a_config.videoelement = videoelement; 190 | a_config.getPlayerAttributeFromVideoElement('someattr').should.equal('sometest'); 191 | }); 192 | 193 | it('should prefer data-[attributename] over [attributename]', () => { 194 | videoelement.setAttribute('someattr','sometest'); 195 | videoelement.setAttribute('data-someattr','sometest2'); 196 | a_config = new Config(videoelement); 197 | a_config.videoelement = videoelement; 198 | a_config.getPlayerAttributeFromVideoElement('someattr').should.equal('sometest2'); 199 | }); 200 | }); 201 | 202 | describe('Skin controls setter', () => { 203 | beforeEach(() => { 204 | sinon.stub(Config.prototype, 'init', () => {}); 205 | a_config = new Config(videoelement); 206 | a_config.videoelement = videoelement; 207 | a_config.options = {}; 208 | }); 209 | 210 | afterEach(() => { 211 | Config.prototype.init.restore(); 212 | }); 213 | 214 | it('should contain ControlBar', () => { 215 | a_config.setSkinControls(); 216 | a_config.options.controlBar.children.should.exist; 217 | }); 218 | }); 219 | 220 | describe('Youtube options setter', () => { 221 | beforeEach(() => { 222 | sinon.stub(Config.prototype, 'init', () => {}); 223 | sinon.stub(Util.prototype, 'isYoutubePlayer', () => { return true; }); 224 | a_config = new Config(videoelement); 225 | a_config.videoelement = videoelement; 226 | a_config.options = {}; 227 | }); 228 | 229 | afterEach(() => { 230 | Config.prototype.init.restore(); 231 | Util.prototype.isYoutubePlayer.restore(); 232 | }); 233 | 234 | it('should set all needed parameters correctly', () => { 235 | sinon.stub(Util.prototype, 'ie', () => { return { actualVersion:0 } }); 236 | 237 | a_config.setYoutubeOptions(); 238 | a_config.options.should.have.all.keys('showinfo','techOrder','sources','youtube'); 239 | a_config.options.youtube['iv_load_policy'].should.exist; 240 | 241 | Util.prototype.ie.restore(); 242 | }); 243 | 244 | it('should set `youtube` parameter with two attributes if IE8 - IE11', () => { 245 | for(var i = 8; i >12; i++){ 246 | var ieMock = { 247 | actualVersion : i 248 | }; 249 | sinon.stub(Util.prototype, 'ie', () => { return ieMock; }); 250 | 251 | a_config = new Config(videoelement); 252 | a_config.videoelement = videoelement; 253 | a_config.options = {}; 254 | 255 | a_config.setYoutubeOptions(); 256 | a_config.options.youtube.should.exist; 257 | a_config.options.youtube.should.have.all.keys('ytControls','color'); 258 | a_config.options.youtube.should.have.length(2); 259 | 260 | Util.prototype.ie.restore(); 261 | } 262 | }); 263 | }); 264 | 265 | describe('CSS class getter', () => { 266 | 267 | beforeEach(() => { 268 | sinon.stub(Config.prototype, 'init', () => {}); 269 | sinon.stub(Util.prototype, 'ie', () => { return { actualVersion: 0 } }); 270 | }); 271 | 272 | afterEach(() => { 273 | Config.prototype.init.restore(); 274 | Util.prototype.ie.restore(); 275 | }); 276 | 277 | it('should return the proper afterglow base class by default', () => { 278 | a_config = new Config(videoelement); 279 | a_config.videoelement = videoelement; 280 | a_config.skin = 'afterglow'; 281 | let providedClass = a_config.getSkinClass(); 282 | providedClass.should.equal('vjs-afterglow-skin'); 283 | }); 284 | 285 | it('should properly include the skin`s name into the class name', () => { 286 | a_config = new Config(videoelement, 'someclass'); 287 | a_config.videoelement = videoelement; 288 | a_config.skin = 'someclass'; 289 | let providedClass = a_config.getSkinClass(); 290 | providedClass.should.equal('vjs-afterglow-skin afterglow-skin-someclass'); 291 | }); 292 | 293 | it('should add a dummy class if IE9', () => { 294 | Util.prototype.ie.restore(); 295 | sinon.stub(Util.prototype, 'ie', () => { return { actualVersion: 9 } }); 296 | a_config = new Config(videoelement); 297 | a_config.videoelement = videoelement; 298 | a_config.skin = 'afterglow'; 299 | let providedClass = a_config.getSkinClass(); 300 | providedClass.should.equal('vjs-afterglow-skin ie9-is-bad'); 301 | }); 302 | }); 303 | 304 | }); -------------------------------------------------------------------------------- /test/test.2.lightbox.js: -------------------------------------------------------------------------------- 1 | import Player from '../src/js/afterglow/components/Player'; 2 | import Lightbox from '../src/js/afterglow/components/Lightbox'; 3 | import Emitter from '../vendor/Emitter/Emitter'; 4 | import Eventbus from '../src/js/afterglow/components/Eventbus'; 5 | import DOMElement from '../src/js/afterglow/lib/DOMElement'; 6 | import Util from '../src/js/afterglow/lib/Util'; 7 | 8 | var chai = require('chai'); 9 | var sinon = require("sinon"); 10 | var sinonChai = require("sinon-chai"); 11 | var jsdom = require('mocha-jsdom'); 12 | 13 | chai.use(sinonChai); 14 | chai.should(); 15 | 16 | var assert = chai.assert; 17 | var expect = chai.expect; 18 | 19 | describe("Afterglow Lightbox", () => { 20 | // Initiate the DOM 21 | jsdom(); 22 | 23 | var lightbox, 24 | $; 25 | 26 | beforeEach(() => { 27 | $ = require('jquery'); 28 | 29 | }); 30 | 31 | describe('constructor', () => { 32 | beforeEach(() => { 33 | sinon.stub(Lightbox.prototype, 'build'); 34 | sinon.stub(DOMElement.prototype, 'addClass'); 35 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 36 | }); 37 | 38 | afterEach(() => { 39 | Lightbox.prototype.build.restore(); 40 | Lightbox.prototype.bindEmitter.restore(); 41 | DOMElement.prototype.addClass.restore(); 42 | }); 43 | 44 | it('should call build() on construction', () => { 45 | lightbox = new Lightbox(); 46 | assert(Lightbox.prototype.build.calledOnce); 47 | }); 48 | it('should use Emitter on construction', () => { 49 | expect(Lightbox.prototype.on).to.be.undefined; 50 | lightbox = new Lightbox(); 51 | assert(Lightbox.prototype.bindEmitter.calledOnce); 52 | }); 53 | it('should create an empty node and pass the lightbox class to it', () => { 54 | expect(Lightbox.prototype.node).to.be.undefined; 55 | lightbox = new Lightbox(); 56 | expect(lightbox.node).to.exist; 57 | expect(DOMElement.prototype.addClass).to.have.been.calledWith('afterglow-lightbox-wrapper'); 58 | }); 59 | }); 60 | 61 | describe('build()', () => { 62 | beforeEach(() => { 63 | sinon.stub(Lightbox.prototype, 'build'); 64 | sinon.stub(DOMElement.prototype, 'addClass'); 65 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 66 | sinon.stub(DOMElement.prototype, 'appendDomElement', (input) => { return input }); 67 | sinon.stub(Lightbox.prototype, 'buildLightbox', () => { return 'lightboxtest' }); 68 | sinon.stub(Lightbox.prototype, 'buildCover', () => { return 'covertest' }); 69 | lightbox = new Lightbox(); 70 | lightbox.build.restore(); 71 | }); 72 | 73 | afterEach(() => { 74 | lightbox.buildLightbox.restore(); 75 | lightbox.buildCover.restore(); 76 | DOMElement.prototype.appendDomElement.restore(); 77 | Lightbox.prototype.bindEmitter.restore(); 78 | DOMElement.prototype.addClass.restore(); 79 | }); 80 | 81 | it('should append 2 elements to itself', () => { 82 | lightbox.build(); 83 | expect(lightbox.appendDomElement).to.have.been.calledTwice; 84 | expect(lightbox.appendDomElement).to.have.been.calledWith('lightboxtest', 'lightbox'); 85 | expect(lightbox.appendDomElement).to.have.been.calledWith('covertest', 'cover'); 86 | }); 87 | it('should build the Lightbox element', () => { 88 | lightbox.build(); 89 | expect(lightbox.buildLightbox).to.have.been.calledOnce 90 | }); 91 | it('should build the Cover element', () => { 92 | lightbox.build(); 93 | expect(lightbox.buildCover).to.have.been.calledOnce 94 | }); 95 | }); 96 | 97 | describe('buildCover()', () => { 98 | beforeEach(() => { 99 | sinon.stub(Lightbox.prototype, 'build'); 100 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 101 | sinon.stub(DOMElement.prototype, 'addClass'); 102 | lightbox = new Lightbox(); 103 | }); 104 | 105 | afterEach(() => { 106 | Lightbox.prototype.bindEmitter.restore(); 107 | DOMElement.prototype.addClass.restore(); 108 | Lightbox.prototype.build.restore(); 109 | }); 110 | 111 | it('should return a DOMElement with a DOM node', () => { 112 | let res = lightbox.buildCover(); 113 | res.should.be.an('object'); 114 | res.node.should.exist; 115 | }); 116 | 117 | it('should add class "cover" properly', () => { 118 | expect(lightbox.addClass).to.have.been.calledOnce; 119 | let res = lightbox.buildCover(); 120 | // First one was in the constructor, second one is here! 121 | expect(res.addClass).to.have.been.calledTwice; 122 | expect(res.addClass).to.have.been.calledWith('cover'); 123 | }); 124 | }); 125 | 126 | describe('buildLightbox()', () => { 127 | beforeEach(() => { 128 | sinon.stub(Lightbox.prototype, 'build'); 129 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 130 | sinon.stub(DOMElement.prototype, 'addClass'); 131 | lightbox = new Lightbox(); 132 | }); 133 | 134 | afterEach(() => { 135 | Lightbox.prototype.bindEmitter.restore(); 136 | DOMElement.prototype.addClass.restore(); 137 | Lightbox.prototype.build.restore(); 138 | }); 139 | 140 | it('should return a DOMElement with a DOM node', () => { 141 | let res = lightbox.buildLightbox(); 142 | res.should.be.an('object'); 143 | res.node.should.exist; 144 | }); 145 | 146 | it('should add class "lightbox" properly', () => { 147 | expect(lightbox.addClass).to.have.been.calledOnce; 148 | let res = lightbox.buildLightbox(); 149 | // First one was in the constructor, second one is here! 150 | expect(res.addClass).to.have.been.calledTwice; 151 | expect(res.addClass).to.have.been.calledWith('afterglow-lightbox'); 152 | }); 153 | }); 154 | 155 | describe('bindEmitter()', () => { 156 | it('should bind Emitter proberly', () => { 157 | sinon.stub(Lightbox.prototype, 'build'); 158 | expect(Lightbox.prototype.on).to.be.undefined; 159 | lightbox = new Lightbox(); 160 | lightbox.on.should.be.a('function'); 161 | Lightbox.prototype.build.restore(); 162 | }) 163 | }); 164 | 165 | describe('calculateLightboxSizes()', () => { 166 | 167 | var window_width, 168 | window_height, 169 | sizetests, 170 | ratiotests; 171 | 172 | beforeEach(() => { 173 | window_width = window.clientWidth || document.documentElement.clientWidth || document.body.clientWidth || window.innerWidth; 174 | window_height = window.clientHeight || document.documentElement.clientHeight || document.body.clientHeight || window.innerHeight; 175 | 176 | sizetests = [ 177 | window_width*0.3, 178 | window_width*0.8, 179 | window_width*0.5, 180 | window_width*1.5, 181 | window_width*5 182 | ]; 183 | ratiotests = [ 184 | 0.5, 0.8, 185 | 1, 186 | 1.4, 1.9, 3, 100 187 | ]; 188 | 189 | sinon.stub(Lightbox.prototype, 'build'); 190 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 191 | sinon.stub(DOMElement.prototype, 'addClass'); 192 | lightbox = new Lightbox(); 193 | }); 194 | 195 | afterEach(() => { 196 | Lightbox.prototype.bindEmitter.restore(); 197 | DOMElement.prototype.addClass.restore(); 198 | Lightbox.prototype.build.restore(); 199 | }); 200 | 201 | it('should scale according to the ratio correctly in landscape format up to the window width', () => { 202 | ratiotests.forEach((ratio) => { 203 | let sizes = lightbox.calculateLightboxSizes(ratio); 204 | 205 | expect(sizes.playerwidth).to.be.below(window_width); 206 | expect(sizes.playerheight).to.be.below(window_height); 207 | expect(sizes.playerheight/sizes.playerwidth).to.be.within(ratio-0.000001, ratio+0.000001); 208 | expect(sizes.playeroffsettop + sizes.playerheight).to.be.most(window_height); 209 | expect(sizes.playeroffsetleft + sizes.playerwidth).to.be.most(window_width); 210 | 211 | if(ratio <= window_height / window_width){ 212 | expect(sizes.playerwidth).to.be.at.least(window_width * .7); 213 | } 214 | else{ 215 | expect(sizes.playerheight).to.be.at.least(window_height * .7); 216 | } 217 | }); 218 | }); 219 | 220 | it('should scale to the maxwidth at max', () => { 221 | sizetests.forEach((max_width) => { 222 | ratiotests.forEach((ratio) => { 223 | let sizes = lightbox.calculateLightboxSizes(ratio, max_width); 224 | 225 | expect(sizes.playerwidth).to.be.below(window_width); 226 | expect(sizes.playerheight).to.be.below(window_height); 227 | expect(sizes.playerheight/sizes.playerwidth).to.be.within(ratio-0.000001, ratio+0.000001); 228 | expect(sizes.playeroffsettop + sizes.playerheight).to.be.most(window_height); 229 | expect(sizes.playeroffsetleft + sizes.playerwidth).to.be.most(window_width); 230 | 231 | if(ratio <= window_height / window_width && max_width > window_width){ 232 | expect(sizes.playerwidth).to.be.at.least(window_width * .7); 233 | } 234 | else if(ratio <= window_height / window_width){ 235 | expect(sizes.playerwidth).to.be.at.most(max_width); 236 | } 237 | else if(ratio > window_height / window_width && max_width > window_width){ 238 | expect(sizes.playerheight).to.be.at.least(window_height * .7); 239 | } 240 | else if(ratio > window_height / window_width){ 241 | expect(sizes.playerheight).to.be.at.most(max_width * ratio); 242 | } 243 | }); 244 | }); 245 | }); 246 | }); 247 | 248 | describe('close()', () => { 249 | beforeEach(() => { 250 | 251 | sinon.stub(Eventbus.prototype, 'dispatch'); 252 | sinon.stub(Lightbox.prototype, 'build'); 253 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 254 | sinon.stub(DOMElement.prototype, 'addClass'); 255 | 256 | window.afterglow = {}; 257 | window.afterglow.eventbus = new Eventbus(); 258 | 259 | lightbox = new Lightbox(); 260 | lightbox.player = { 261 | destroy : () => {} 262 | }; 263 | lightbox.node = { 264 | parentNode : { 265 | removeChild : () => {} 266 | } 267 | }; 268 | lightbox.emit = (input) => {}; 269 | 270 | sinon.spy(lightbox.player, 'destroy'); 271 | sinon.spy(lightbox, 'emit'); 272 | sinon.spy(lightbox.node.parentNode, 'removeChild'); 273 | }); 274 | 275 | afterEach(() => { 276 | Lightbox.prototype.bindEmitter.restore(); 277 | DOMElement.prototype.addClass.restore(); 278 | Lightbox.prototype.build.restore(); 279 | Eventbus.prototype.dispatch.restore(); 280 | }); 281 | 282 | it('should properly trigger the destroy method on the player', () => { 283 | lightbox.close(); 284 | expect(lightbox.player.destroy).to.have.been.calledOnce; 285 | }); 286 | 287 | it('should properly remove the nodes from the DOM', () => { 288 | lightbox.close(); 289 | expect(lightbox.node.parentNode.removeChild).to.have.been.calledOnce; 290 | }); 291 | 292 | it('should emit the closing event', () => { 293 | lightbox.close(); 294 | assert(Eventbus.prototype.dispatch.calledOnce); 295 | expect(lightbox.emit).to.have.been.calledOnce; 296 | expect(lightbox.emit).to.have.been.calledWith('close'); 297 | }); 298 | }); 299 | 300 | describe('getPlayer()', () => { 301 | beforeEach(() => { 302 | sinon.stub(Lightbox.prototype, 'build'); 303 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 304 | sinon.stub(DOMElement.prototype, 'addClass'); 305 | lightbox = new Lightbox(); 306 | }); 307 | 308 | afterEach(() => { 309 | Lightbox.prototype.bindEmitter.restore(); 310 | DOMElement.prototype.addClass.restore(); 311 | Lightbox.prototype.build.restore(); 312 | }); 313 | 314 | it('should properly return the players videojs instance', () => { 315 | lightbox.player = { 316 | getPlayer: function(){ return this.videojs} 317 | }; 318 | lightbox.player.videojs = 'test'; 319 | let res = lightbox.getPlayer(); 320 | res.should.equal('test'); 321 | }); 322 | 323 | it('should return undefined if the player does not exist', () => { 324 | let res = lightbox.getPlayer(); 325 | expect(res).to.be.undefined; 326 | }) 327 | }); 328 | 329 | describe('resize() with regular players', () => { 330 | beforeEach(() => { 331 | sinon.stub(Lightbox.prototype, 'build'); 332 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 333 | sinon.stub(DOMElement.prototype, 'addClass'); 334 | lightbox = new Lightbox(); 335 | lightbox.lightbox = { 336 | node : { 337 | style: {} 338 | } 339 | }; 340 | lightbox.lightbox.videoelement = { 341 | getAttribute : () => { return 0.6 } 342 | }; 343 | sinon.stub(lightbox, 'calculateLightboxSizes', () => { 344 | return { 345 | playerwidth : 1, 346 | playerheight : 2, 347 | playeroffsettop: 3, 348 | playeroffsetleft: 4, 349 | width: 5, 350 | height: 6 351 | } 352 | }); 353 | lightbox.node = { 354 | style : {} 355 | }; 356 | lightbox.emit = (input) => {}; 357 | }); 358 | 359 | afterEach(() => { 360 | Lightbox.prototype.bindEmitter.restore(); 361 | DOMElement.prototype.addClass.restore(); 362 | Lightbox.prototype.build.restore(); 363 | }); 364 | 365 | it('should pass the ratio to the calculation function correctly', () => { 366 | lightbox.resize(); 367 | expect(lightbox.calculateLightboxSizes).to.have.been.calledWith(0.6); 368 | }); 369 | 370 | it('should pass correctly style the underlying elements', () => { 371 | lightbox.resize(); 372 | lightbox.node.style.width.should.equal(5); 373 | lightbox.node.style.height.should.equal(6); 374 | lightbox.lightbox.node.style.height.should.equal("2px"); 375 | lightbox.lightbox.node.style.width.should.equal("1px"); 376 | lightbox.lightbox.node.style.top.should.equal("3px"); 377 | lightbox.lightbox.node.style.left.should.equal("4px"); 378 | }); 379 | 380 | it('should pass maxwidth to the scaling method if needed', () => { 381 | lightbox.lightbox.videoelement = { 382 | getAttribute : (input) => { 383 | if(input == 'data-maxwidth'){ 384 | return '123' 385 | } 386 | return 'false' 387 | } 388 | }; 389 | lightbox.resize(); 390 | 391 | expect(lightbox.calculateLightboxSizes).to.have.been.calledWith('false', 123); 392 | }); 393 | }); 394 | 395 | describe('resize() with yt', () => { 396 | beforeEach(() => { 397 | sinon.stub(Lightbox.prototype, 'build'); 398 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 399 | sinon.stub(DOMElement.prototype, 'addClass'); 400 | lightbox = new Lightbox(); 401 | lightbox.lightbox = { 402 | node : { 403 | style: {} 404 | } 405 | }; 406 | lightbox.lightbox.videoelement = undefined; 407 | sinon.stub(lightbox, 'calculateLightboxSizes', () => { 408 | return { 409 | playerwidth : 1, 410 | playerheight : 2, 411 | playeroffsettop: 3, 412 | playeroffsetleft: 4, 413 | width: 5, 414 | height: 6 415 | } 416 | }); 417 | lightbox.node = { 418 | style : {} 419 | }; 420 | lightbox.emit = (input) => {}; 421 | 422 | // Fake lightbox behaviour 423 | document.body.innerHTML = '
'; 424 | }); 425 | 426 | afterEach(() => { 427 | Lightbox.prototype.bindEmitter.restore(); 428 | DOMElement.prototype.addClass.restore(); 429 | Lightbox.prototype.build.restore(); 430 | }); 431 | 432 | it('should pass the ratio to the calculation function correctly', () => { 433 | lightbox.resize(); 434 | expect(lightbox.calculateLightboxSizes).to.have.been.calledWith('.7'); 435 | }); 436 | 437 | it('should pass correctly style the underlying elements', () => { 438 | lightbox.resize(); 439 | lightbox.node.style.width.should.equal(5); 440 | lightbox.node.style.height.should.equal(6); 441 | lightbox.lightbox.node.style.height.should.equal("2px"); 442 | lightbox.lightbox.node.style.width.should.equal("1px"); 443 | lightbox.lightbox.node.style.top.should.equal("3px"); 444 | lightbox.lightbox.node.style.left.should.equal("4px"); 445 | }); 446 | }); 447 | 448 | describe('passVideoElement()', () => { 449 | 450 | var lightbox, 451 | mockelement; 452 | 453 | beforeEach(() => { 454 | sinon.stub(Lightbox.prototype, 'build'); 455 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 456 | sinon.stub(DOMElement.prototype, 'addClass'); 457 | sinon.stub(Player.prototype, 'setup'); 458 | 459 | lightbox = new Lightbox(); 460 | 461 | document.body.innerHTML = ''; 462 | mockelement = document.querySelector('.videoelement'); 463 | 464 | lightbox.lightbox = { 465 | appendDomElement: (i1, i2) => { 466 | return true; 467 | } 468 | }; 469 | sinon.spy(lightbox.lightbox, 'appendDomElement'); 470 | }); 471 | 472 | afterEach(() => { 473 | Lightbox.prototype.bindEmitter.restore(); 474 | DOMElement.prototype.addClass.restore(); 475 | Lightbox.prototype.build.restore(); 476 | Player.prototype.setup.restore(); 477 | }); 478 | 479 | it('should pass the id to the element properly', () => { 480 | lightbox.passVideoElement(mockelement); 481 | lightbox.playerid.should.equal('sometestid'); 482 | }); 483 | 484 | it('should create a new DOMElement from the element and pass it to the lightbox', () => { 485 | lightbox.passVideoElement(mockelement); 486 | expect(lightbox.lightbox.videoelement.node).to.equal(mockelement); 487 | }); 488 | 489 | it('should make the player autoplay', () => { 490 | lightbox.passVideoElement(mockelement); 491 | expect(lightbox.lightbox.videoelement.node.getAttribute('autoplay')).to.equal('autoplay'); 492 | }); 493 | 494 | it('should create a new player', () => { 495 | lightbox.passVideoElement(mockelement); 496 | expect(Player.prototype.setup).to.have.been.calledOnce; 497 | expect(Player.prototype.setup).to.have.been.calledWith(lightbox.lightbox.videoelement); 498 | }); 499 | }); 500 | 501 | describe('launch (except player init callback)', () => { 502 | beforeEach(() => { 503 | sinon.stub(Lightbox.prototype, 'build'); 504 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 505 | sinon.stub(DOMElement.prototype, 'addClass'); 506 | document.body.innerHTML = ''; 507 | 508 | window['videojs'] = { players : { 509 | video1 : { 510 | id_ : 'video1', 511 | paused:false, 512 | pause: () => { 513 | window.videojs.players['video1'].paused = true; 514 | } 515 | }, 516 | video2 : { 517 | id_ : 'video2', 518 | paused:false, 519 | pause: () => { 520 | window.videojs.players['video2'].paused = true; 521 | } 522 | } 523 | }}; 524 | window.videojs.getPlayers = function(){ 525 | return window.videojs.players; 526 | } 527 | 528 | //, 529 | // sinon.stub(Util.prototype, 'isMobile'); 530 | // sinon.stub(Util.prototype, 'addEventListener'); 531 | 532 | lightbox = new Lightbox(); 533 | lightbox.player = { 534 | init : () => {} 535 | } 536 | lightbox.cover = { 537 | bind : () => {} 538 | } 539 | sinon.stub(lightbox.player, 'init', (_callback) => { 540 | 541 | }); 542 | sinon.stub(lightbox, 'resize'); 543 | sinon.stub(lightbox, 'close'); 544 | sinon.stub(lightbox.cover, 'bind'); 545 | }); 546 | 547 | afterEach(() => { 548 | Lightbox.prototype.bindEmitter.restore(); 549 | DOMElement.prototype.addClass.restore(); 550 | Lightbox.prototype.build.restore(); 551 | }); 552 | 553 | it('should pass the element node to the dom correctly', () => { 554 | expect(document.body.innerHTML).to.equal(''); 555 | lightbox.launch(); 556 | expect(document.body.innerHTML).to.equal('
'); 557 | }); 558 | 559 | it('should init the player', () => { 560 | lightbox.launch(); 561 | expect(lightbox.player.init).to.have.been.calledOnce; 562 | }); 563 | 564 | it('should resize the event and bind resizing to windows resize event', () => { 565 | sinon.stub(Util.prototype, 'addEventListener', (i1,i2,i3) => { return i3({keyCode:0}); }); 566 | lightbox.launch(); 567 | expect(lightbox.resize).to.have.been.calledTwice; 568 | Util.prototype.addEventListener.restore(); 569 | }); 570 | 571 | it('should pass close() to cover on click event', () => { 572 | lightbox.cover.bind.restore(); 573 | sinon.stub(lightbox.cover, 'bind', (i1, i2) => { i2(); }); 574 | lightbox.launch(); 575 | expect(lightbox.close).to.have.been.calledOnce; 576 | }); 577 | 578 | it('should pass the closing event on escape key properly', () => { 579 | sinon.stub(Util.prototype, 'addEventListener', (i1,i2,i3) => { return i3({keyCode:0}); }); 580 | lightbox.launch(); 581 | expect(lightbox.close).to.not.have.been.called; 582 | Util.prototype.addEventListener.restore(); 583 | 584 | sinon.stub(Util.prototype, 'addEventListener', (i1,i2,i3) => { return i3({keyCode:27}); }); 585 | lightbox.launch(); 586 | expect(lightbox.close).to.have.been.calledOnce; 587 | Util.prototype.addEventListener.restore(); 588 | }); 589 | 590 | it('should call the callback function when done', () => { 591 | var test = 1; 592 | lightbox.launch(); 593 | expect(test).to.equal(1); 594 | lightbox.launch(() => {test++}); 595 | expect(test).to.equal(2); 596 | lightbox.launch(test); 597 | expect(test).to.equal(2); 598 | lightbox.launch(() => {test++}); 599 | expect(test).to.equal(3); 600 | }); 601 | 602 | it('should stop all other videos when launching', () => { 603 | expect(window.videojs.players['video1'].paused).to.be.false; 604 | expect(window.videojs.players['video2'].paused).to.be.false; 605 | lightbox.launch(); 606 | expect(window.videojs.players['video1'].paused).to.be.true; 607 | expect(window.videojs.players['video2'].paused).to.be.true; 608 | }); 609 | }); 610 | 611 | describe('launch (only player init callback)', () => { 612 | 613 | var mockObject; 614 | 615 | beforeEach(() => { 616 | sinon.stub(Lightbox.prototype, 'build'); 617 | sinon.stub(Lightbox.prototype, 'bindEmitter'); 618 | sinon.stub(DOMElement.prototype, 'addClass'); 619 | sinon.stub(Util.prototype, 'isMobile'); 620 | document.body.innerHTML = ''; 621 | 622 | mockObject = { 623 | addChild : () => {} 624 | } 625 | 626 | let videojs = { 627 | paused : () => { return false; }, 628 | on : (i1, i2) => {}, 629 | posterImage : { 630 | show : () => {} 631 | }, 632 | bigPlayButton : { 633 | show : () => {} 634 | }, 635 | getChild : () => { 636 | return mockObject; 637 | } 638 | } 639 | 640 | lightbox = new Lightbox(); 641 | lightbox.player = { 642 | init : () => {}, 643 | videojs: videojs 644 | } 645 | lightbox.cover = { 646 | bind : () => {} 647 | } 648 | lightbox.lightbox = {}; 649 | lightbox.lightbox.videoelement = document.createElement('div'); 650 | sinon.stub(lightbox.player, 'init', (_callback) => { 651 | _callback(); 652 | }); 653 | sinon.stub(lightbox, 'resize'); 654 | sinon.stub(lightbox, 'close'); 655 | }); 656 | 657 | afterEach(() => { 658 | Lightbox.prototype.bindEmitter.restore(); 659 | DOMElement.prototype.addClass.restore(); 660 | Lightbox.prototype.build.restore(); 661 | Util.prototype.isMobile.restore(); 662 | }); 663 | 664 | it('should pass LightboxCloseButton to the TopControlBar', () => { 665 | sinon.spy(lightbox.player.videojs, "getChild"); 666 | sinon.spy(mockObject, "addChild"); 667 | lightbox.launch(); 668 | expect(mockObject.addChild).to.have.been.calledOnce; 669 | expect(lightbox.player.videojs.getChild).to.have.been.calledOnce; 670 | expect(mockObject.addChild).to.have.been.calledWith("LightboxCloseButton"); 671 | mockObject.addChild.restore(); 672 | }); 673 | 674 | it('should make the poster show if autoclose has not been requested (by default)', () => { 675 | sinon.stub(lightbox.player.videojs, "on", (i1, i2) => { 676 | i2(); 677 | }); 678 | sinon.spy(lightbox.player.videojs.posterImage, "show"); 679 | lightbox.launch(); 680 | expect(lightbox.player.videojs.posterImage.show).to.have.been.calledOnce; 681 | }); 682 | 683 | it('should make the lightbox autoclose if autoclose has been requested', () => { 684 | sinon.stub(lightbox.player.videojs, "on", (i1, i2) => { 685 | i2(); 686 | }); 687 | sinon.stub(lightbox.lightbox.videoelement, "getAttribute", () => { 688 | return "true" 689 | }); 690 | lightbox.launch(); 691 | expect(lightbox.close).to.have.been.calledOnce; 692 | }); 693 | 694 | it('should trigger the section to show poster and playbutton only if not mobile', () => { 695 | sinon.spy(lightbox.player.videojs,"paused"); 696 | sinon.spy(lightbox.player.videojs.posterImage, "show"); 697 | sinon.spy(lightbox.player.videojs.bigPlayButton, "show"); 698 | 699 | Util.prototype.isMobile.restore(); 700 | sinon.stub(Util.prototype, "isMobile", () => { return true}); 701 | lightbox.launch(); 702 | 703 | expect(Util.prototype.isMobile).to.have.been.calledOnce; 704 | expect(lightbox.player.videojs.paused).to.not.have.been.called; 705 | expect(lightbox.player.videojs.posterImage.show).to.not.have.been.called; 706 | expect(lightbox.player.videojs.bigPlayButton.show).to.not.have.been.called; 707 | }); 708 | 709 | it('should not display poster and playbutton if autoplay did not fail but check for it', () => { 710 | sinon.spy(lightbox.player.videojs,"paused"); 711 | sinon.spy(lightbox.player.videojs.posterImage, "show"); 712 | sinon.spy(lightbox.player.videojs.bigPlayButton, "show"); 713 | Util.prototype.isMobile.restore(); 714 | sinon.stub(Util.prototype, "isMobile", () => { return false}); 715 | 716 | lightbox.launch(); 717 | expect(Util.prototype.isMobile).to.have.been.calledOnce; 718 | expect(lightbox.player.videojs.paused).to.have.been.calledOnce; 719 | expect(lightbox.player.videojs.posterImage.show).to.not.have.been.called; 720 | expect(lightbox.player.videojs.bigPlayButton.show).to.not.have.been.called; 721 | }); 722 | 723 | it('should show poster and playbutton if autoplay failed', () => { 724 | sinon.stub(lightbox.player.videojs,"paused",() => { return true}); 725 | sinon.spy(lightbox.player.videojs.posterImage, "show"); 726 | sinon.spy(lightbox.player.videojs.bigPlayButton, "show"); 727 | Util.prototype.isMobile.restore(); 728 | sinon.stub(Util.prototype, "isMobile", () => { return false}); 729 | 730 | lightbox.launch(); 731 | expect(Util.prototype.isMobile).to.have.been.calledOnce; 732 | expect(lightbox.player.videojs.paused).to.have.been.calledOnce; 733 | expect(lightbox.player.videojs.posterImage.show).to.have.been.calledOnce; 734 | expect(lightbox.player.videojs.bigPlayButton.show).to.have.been.calledOnce; 735 | }); 736 | }); 737 | }); -------------------------------------------------------------------------------- /test/test.2.lightbox_trigger.js: -------------------------------------------------------------------------------- 1 | import LightboxTrigger from '../src/js/afterglow/components/LightboxTrigger'; 2 | import Lightbox from '../src/js/afterglow/components/Lightbox'; 3 | import Eventbus from '../src/js/afterglow/components/Eventbus'; 4 | import Emitter from '../vendor/Emitter/Emitter'; 5 | import DOMElement from '../src/js/afterglow/lib/DOMElement'; 6 | 7 | var chai = require('chai'); 8 | var sinon = require("sinon"); 9 | var sinonChai = require("sinon-chai"); 10 | var jsdom = require('mocha-jsdom'); 11 | 12 | chai.use(sinonChai); 13 | chai.should(); 14 | 15 | var assert = chai.assert; 16 | var expect = chai.expect; 17 | 18 | describe("Afterglow Lightbox Trigger", () => { 19 | // Initiate the DOM 20 | jsdom(); 21 | 22 | var afterglow, 23 | lightboxtrigger, 24 | triggerelement, 25 | $; 26 | 27 | beforeEach(() => { 28 | $ = require('jquery'); 29 | 30 | document.body.innerHTML = ''; 31 | triggerelement = document.querySelector('a.afterglow'); 32 | }); 33 | 34 | describe('constructor', () => { 35 | it('should call init()', () => { 36 | sinon.stub(LightboxTrigger.prototype, 'init'); 37 | lightboxtrigger = new LightboxTrigger(triggerelement); 38 | assert(LightboxTrigger.prototype.init.calledOnce); 39 | LightboxTrigger.prototype.init.restore(); 40 | }); 41 | }); 42 | 43 | describe('init()', () => { 44 | beforeEach(() => { 45 | sinon.stub(LightboxTrigger.prototype, 'prepare'); 46 | lightboxtrigger = new LightboxTrigger(triggerelement); 47 | }); 48 | 49 | afterEach(() => { 50 | LightboxTrigger.prototype.prepare.restore(); 51 | }); 52 | 53 | it('should get the correct playerid', () => { 54 | lightboxtrigger.playerid.should.equal('testid'); 55 | }); 56 | 57 | it('should get the video element properly', () => { 58 | let videoelement = document.querySelector('#testid'); 59 | // This isn't a proper solution because it still relies on DOMElement to work. But as this class is also tested, this should do for testings. 60 | lightboxtrigger.videoelement.node.should.equal(videoelement); 61 | }); 62 | 63 | it('should call prepare()', () => { 64 | assert(LightboxTrigger.prototype.prepare.calledOnce); 65 | }); 66 | 67 | it('should use Emitter for itself', () => { 68 | // This isn't a proper solution because it relies on Emitter to work. But as this class is also tested, this should do for testings. 69 | lightboxtrigger.on.should.be.a('function'); 70 | }); 71 | }); 72 | 73 | describe('prepare()', () => { 74 | beforeEach(() => { 75 | sinon.stub(LightboxTrigger.prototype, 'prepare'); 76 | lightboxtrigger = new LightboxTrigger(triggerelement); 77 | LightboxTrigger.prototype.prepare.restore(); 78 | 79 | lightboxtrigger.videoelement = { 80 | addClass : (input) => {}, 81 | setAttribute : (input1, input2) => {} 82 | } 83 | }); 84 | 85 | it('should make the video element have autoresize fit by default', () => { 86 | sinon.stub(lightboxtrigger, 'bind'); 87 | sinon.spy(lightboxtrigger.videoelement, 'setAttribute'); 88 | 89 | lightboxtrigger.prepare(); 90 | 91 | expect(lightboxtrigger.videoelement.setAttribute).to.have.been.calledOnce; 92 | expect(lightboxtrigger.videoelement.setAttribute).to.have.been.calledWith('data-autoresize', 'fit'); 93 | 94 | }); 95 | 96 | it('should add the CSS class properly', () => { 97 | sinon.stub(lightboxtrigger, 'bind'); 98 | sinon.spy(lightboxtrigger.videoelement, 'addClass'); 99 | 100 | lightboxtrigger.prepare(); 101 | 102 | expect(lightboxtrigger.videoelement.addClass).to.have.been.calledOnce; 103 | expect(lightboxtrigger.videoelement.addClass).to.have.been.calledWith('afterglow-lightboxplayer'); 104 | 105 | }); 106 | }); 107 | 108 | describe('trigger()', () => { 109 | beforeEach(() => { 110 | 111 | sinon.stub(Eventbus.prototype, 'dispatch'); 112 | sinon.stub(LightboxTrigger.prototype, 'init'); 113 | sinon.stub(Emitter.prototype, 'on'); 114 | sinon.stub(Emitter.prototype, 'emit'); 115 | lightboxtrigger = new LightboxTrigger(triggerelement); 116 | sinon.stub(Lightbox.prototype, 'build', function(){ this.on = () => { return 'test' } }); 117 | sinon.stub(Lightbox.prototype, 'passVideoElement', (input) => { return input }); 118 | sinon.stub(Lightbox.prototype, 'launch'); 119 | 120 | window.afterglow = {}; 121 | window.afterglow.eventbus = new Eventbus(); 122 | 123 | lightboxtrigger.videoelement = { 124 | cloneNode : () => { return 'test' } 125 | } 126 | lightboxtrigger.emit = () => { 127 | return 128 | }; 129 | }); 130 | 131 | afterEach(() => { 132 | LightboxTrigger.prototype.init.restore(); 133 | Lightbox.prototype.build.restore(); 134 | Lightbox.prototype.passVideoElement.restore(); 135 | Lightbox.prototype.launch.restore(); 136 | Emitter.prototype.on.restore(); 137 | Emitter.prototype.emit.restore(); 138 | Eventbus.prototype.dispatch.restore(); 139 | }); 140 | 141 | it('should create a new Lightbox Element', () => { 142 | lightboxtrigger.trigger(); 143 | lightboxtrigger.lightbox.should.be.an('object'); 144 | }); 145 | 146 | it('should pass a clone of the video element node to the lightbox', () => { 147 | sinon.spy(lightboxtrigger.videoelement, 'cloneNode'); 148 | var passedinput; 149 | lightboxtrigger.trigger(); 150 | assert(Lightbox.prototype.passVideoElement.calledOnce); 151 | assert(lightboxtrigger.videoelement.cloneNode.calledOnce); 152 | expect(Lightbox.prototype.passVideoElement).to.have.been.calledWith('test'); 153 | }); 154 | 155 | it('should launch the lightbox properly', () => { 156 | lightboxtrigger.trigger(); 157 | assert(Lightbox.prototype.launch.calledOnce); 158 | assert(Eventbus.prototype.dispatch.calledOnce); 159 | }); 160 | 161 | it('should trigger and bind the events', () => { 162 | sinon.stub(lightboxtrigger, 'emit'); 163 | lightboxtrigger.trigger(); 164 | assert(lightboxtrigger.emit.calledOnce); 165 | }); 166 | }); 167 | 168 | describe('closeLightbox()', () => { 169 | 170 | beforeEach(() => { 171 | sinon.stub(LightboxTrigger.prototype, 'prepare'); 172 | lightboxtrigger = new LightboxTrigger(triggerelement); 173 | 174 | window.test = 1; 175 | 176 | lightboxtrigger.lightbox = { 177 | close : function(){ 178 | window.test = 2; 179 | } 180 | } 181 | 182 | sinon.stub(lightboxtrigger.lightbox, 'close'); 183 | sinon.stub(lightboxtrigger, 'deleteLightbox'); 184 | }); 185 | 186 | afterEach(() => { 187 | LightboxTrigger.prototype.prepare.restore(); 188 | }); 189 | 190 | it('should pass the closing to the related lightbox', () => { 191 | lightboxtrigger.closeLightbox(); 192 | assert(lightboxtrigger.lightbox.close.calledOnce); 193 | }); 194 | 195 | it('should delete the lightbox when closing it', () => { 196 | lightboxtrigger.closeLightbox(); 197 | assert(lightboxtrigger.deleteLightbox.calledOnce); 198 | }); 199 | }); 200 | 201 | describe('deleteLightbox()', () => { 202 | 203 | beforeEach(() => { 204 | sinon.stub(LightboxTrigger.prototype, 'prepare'); 205 | lightboxtrigger = new LightboxTrigger(triggerelement); 206 | 207 | lightboxtrigger.lightbox = { 208 | close : () => {} 209 | } 210 | }); 211 | 212 | afterEach(() => { 213 | LightboxTrigger.prototype.prepare.restore(); 214 | }); 215 | 216 | it('should delete the element from the current trigger', () => { 217 | lightboxtrigger.lightbox.should.be.an('object'); 218 | lightboxtrigger.deleteLightbox(); 219 | assert.isUndefined(lightboxtrigger.lightbox); 220 | }); 221 | }); 222 | 223 | describe('getPlayer()', () => { 224 | 225 | beforeEach(() => { 226 | sinon.stub(LightboxTrigger.prototype, 'prepare'); 227 | lightboxtrigger = new LightboxTrigger(triggerelement); 228 | 229 | lightboxtrigger.lightbox = { 230 | getPlayer : () => { return 'test' } 231 | } 232 | }); 233 | 234 | afterEach(() => { 235 | LightboxTrigger.prototype.prepare.restore(); 236 | }); 237 | 238 | it('should pass the get Player event to the underlying lightbox', () => { 239 | var res = lightboxtrigger.getPlayer(); 240 | res.should.equal('test'); 241 | }); 242 | }); 243 | 244 | }); -------------------------------------------------------------------------------- /test/test.3.dom-element.js: -------------------------------------------------------------------------------- 1 | import DOMElement from '../src/js/afterglow/lib/DOMElement'; 2 | 3 | var chai = require('chai'); 4 | var sinon = require("sinon"); 5 | var sinonChai = require("sinon-chai"); 6 | var jsdom = require('mocha-jsdom'); 7 | 8 | chai.use(sinonChai); 9 | chai.should(); 10 | 11 | var assert = chai.assert; 12 | var expect = chai.expect; 13 | 14 | describe("DOMElement", () => { 15 | // Initiate the DOM 16 | jsdom(); 17 | 18 | var domelement, 19 | $; 20 | 21 | beforeEach(() => { 22 | $ = require('jquery'); 23 | }); 24 | 25 | describe('Constructor', () => { 26 | it('should contain its real DOM element',() => { 27 | document.body.innerHTML = ''; 28 | let videoelement = document.querySelector('video'); 29 | let domelement = new DOMElement(videoelement); 30 | domelement.node.should.equal(videoelement); 31 | }); 32 | }); 33 | 34 | describe('Class methods', () => { 35 | it('should add a CSS class via addClass()',() => { 36 | document.body.innerHTML = ''; 37 | let videoelement = document.querySelector('video'); 38 | let domelement = new DOMElement(videoelement); 39 | document.querySelectorAll('video.testclass').should.have.length(0); 40 | domelement.addClass('testclass'); 41 | document.querySelectorAll('video.testclass').should.have.length(1); 42 | }); 43 | it('should remove a CSS class via removeClass()',() => { 44 | document.body.innerHTML = ''; 45 | let videoelement = document.querySelector('video'); 46 | let domelement = new DOMElement(videoelement); 47 | document.querySelectorAll('video.afterglow').should.have.length(1); 48 | domelement.removeClass('afterglow'); 49 | document.querySelectorAll('video.afterglow').should.have.length(0); 50 | }); 51 | it('should properly detect a CSS class via hasClass()',() => { 52 | document.body.innerHTML = ''; 53 | let videoelement = document.querySelector('video'); 54 | let domelement = new DOMElement(videoelement); 55 | domelement.hasClass('afterglow').should.be.true; 56 | domelement.hasClass('someotherclass').should.be.false; 57 | }); 58 | }); 59 | 60 | describe('Event methods', () => { 61 | it('should properly bind events', () => { 62 | document.body.innerHTML = ''; 63 | let videoelement = document.querySelector('video'); 64 | let domelement = new DOMElement(videoelement); 65 | 66 | var method, handler; 67 | var getMethod = (m) => { method = m }; 68 | var getHandler = (h) => { handler = h }; 69 | sinon.stub(domelement.node, 'addEventListener', (m,h) => { getMethod(m); getHandler(h) }); 70 | 71 | let res = domelement.bind('someevent', 'something'); 72 | 73 | assert(domelement.node.addEventListener.calledOnce); 74 | method.should.equal('someevent'); 75 | handler.should.equal( 'something' ); 76 | }); 77 | it('should properly split up multiple events and bind all of them', () => { 78 | document.body.innerHTML = ''; 79 | let videoelement = document.querySelector('video'); 80 | let domelement = new DOMElement(videoelement); 81 | sinon.stub(domelement.node, 'addEventListener', () => { }); 82 | 83 | let res = domelement.bind('someevent someotherevent'); 84 | 85 | assert(domelement.node.addEventListener.calledTwice); 86 | }); 87 | it('should properly unbind events', () => { 88 | document.body.innerHTML = ''; 89 | let videoelement = document.querySelector('video'); 90 | let domelement = new DOMElement(videoelement); 91 | 92 | var method, handler; 93 | var getMethod = (m) => { method = m }; 94 | var getHandler = (h) => { handler = h }; 95 | sinon.stub(domelement.node, 'removeEventListener', (m,h) => { getMethod(m); getHandler(h) }); 96 | 97 | let res = domelement.unbind('someevent', 'something'); 98 | 99 | assert(domelement.node.removeEventListener.calledOnce); 100 | method.should.equal('someevent'); 101 | handler.should.equal('something'); 102 | }); 103 | it('should properly split up multiple events and unbind all of them', () => { 104 | document.body.innerHTML = ''; 105 | let videoelement = document.querySelector('video'); 106 | let domelement = new DOMElement(videoelement); 107 | sinon.stub(domelement.node, 'removeEventListener', () => { }); 108 | 109 | let res = domelement.unbind('someevent someotherevent'); 110 | 111 | assert(domelement.node.removeEventListener.calledTwice); 112 | }); 113 | }); 114 | 115 | describe('Proxy methods', () => { 116 | var domelement; 117 | 118 | beforeEach(() => { 119 | document.body.innerHTML = '
'; 120 | let rawnode = document.querySelector('div'); 121 | domelement = new DOMElement(rawnode); 122 | }); 123 | 124 | it('should pass getAttribute to node', () => { 125 | sinon.stub(domelement.node, 'getAttribute', (input) => { return input; }); 126 | 127 | let res = domelement.getAttribute('something'); 128 | 129 | assert(domelement.node.getAttribute.calledOnce); 130 | res.should.equal('something'); 131 | }); 132 | it('should pass setAttribute to node', () => { 133 | sinon.stub(domelement.node, 'setAttribute', (input1, input2) => { return input1 + input2; }); 134 | 135 | let res = domelement.setAttribute('something', 'someotherthing'); 136 | 137 | assert(domelement.node.setAttribute.calledOnce); 138 | res.should.equal('somethingsomeotherthing'); 139 | }); 140 | it('should pass hasAttribute to node', () => { 141 | sinon.stub(domelement.node, 'hasAttribute', (input1) => { return input1; }); 142 | 143 | let res = domelement.hasAttribute('something', 'someotherthing'); 144 | 145 | assert(domelement.node.hasAttribute.calledOnce); 146 | res.should.equal('something'); 147 | }); 148 | it('should pass cloneNode to the node element', () => { 149 | sinon.stub(domelement.node, 'cloneNode', (input = false) => { return input; }); 150 | 151 | let res = domelement.cloneNode('somevalue'); 152 | 153 | assert(domelement.node.cloneNode.calledOnce); 154 | res.should.equal('somevalue'); 155 | 156 | res = domelement.cloneNode(); 157 | 158 | assert(domelement.node.cloneNode.calledTwice); 159 | res.should.be.false; 160 | }); 161 | it('should pass removeAttribute to the node element', () => { 162 | sinon.stub(domelement.node, 'removeAttribute', (input = false) => { return input; }); 163 | 164 | let res = domelement.removeAttribute('somevalue'); 165 | 166 | assert(domelement.node.removeAttribute.calledOnce); 167 | res.should.equal('somevalue'); 168 | 169 | res = domelement.removeAttribute(); 170 | 171 | assert(domelement.node.removeAttribute.calledTwice); 172 | res.should.be.false; 173 | }); 174 | it('should pass appendChild to the node element', () => { 175 | sinon.stub(domelement.node, 'appendChild', (input = false) => { return input; }); 176 | domelement.appendChild('somevalue'); 177 | assert(domelement.node.appendChild.calledOnce); 178 | domelement.appendChild(); 179 | assert(domelement.node.appendChild.calledTwice); 180 | }); 181 | it('should append child node via appendChild when appendDomElement is called', () => { 182 | document.body.innerHTML = '

'; 183 | let rawnode2 = document.querySelector('p'); 184 | var domelement2 = new DOMElement(rawnode2); 185 | 186 | sinon.stub(domelement.node, 'appendChild', (input = false) => { return input; }); 187 | sinon.stub(domelement2.node, 'appendChild', (input = false) => { return input; }); 188 | 189 | domelement.appendDomElement(domelement2, 'somevalue'); 190 | assert(domelement.node.appendChild.calledOnce);; 191 | }); 192 | it('should append the element properly to the DOMElement when appendDomElement is called', () => { 193 | document.body.innerHTML = '

'; 194 | let rawnode2 = document.querySelector('p'); 195 | var domelement2 = new DOMElement(rawnode2); 196 | 197 | sinon.stub(domelement.node, 'appendChild'); 198 | sinon.stub(domelement2.node, 'appendChild'); 199 | 200 | domelement.appendDomElement(domelement2, 'somevalue'); 201 | domelement.somevalue.should.equal(domelement2); 202 | }); 203 | }); 204 | 205 | 206 | }); -------------------------------------------------------------------------------- /test/test.3.eventbus.js: -------------------------------------------------------------------------------- 1 | import Eventbus from '../src/js/afterglow/components/Eventbus'; 2 | 3 | var chai = require('chai'); 4 | var sinon = require("sinon"); 5 | var sinonChai = require("sinon-chai"); 6 | var jsdom = require('mocha-jsdom'); 7 | 8 | chai.use(sinonChai); 9 | chai.should(); 10 | 11 | var assert = chai.assert; 12 | var expect = chai.expect; 13 | 14 | describe("Eventbus", () => { 15 | var eventbus, testCallback; 16 | 17 | beforeEach(() => { 18 | eventbus = new Eventbus(); 19 | testCallback = sinon.spy(); 20 | }); 21 | 22 | describe('subscribe()', () => { 23 | it('should bind events',() => { 24 | eventbus.subscribe('playerid','event', testCallback); 25 | eventbus.players['playerid'].listeners.should.have.property('event'); 26 | }); 27 | }); 28 | 29 | describe('unsubscribe()', () => { 30 | it('should remove callbacks',() => { 31 | eventbus.players = { 32 | playerid : { 33 | listeners : { 34 | 'event1': [testCallback, testCallback] 35 | } 36 | } 37 | }; 38 | eventbus.unsubscribe('playerid','event1', testCallback); 39 | eventbus.players['playerid'].listeners.should.have.property('event1').with.lengthOf(1); 40 | }); 41 | }); 42 | 43 | 44 | describe('dispatch()', () => { 45 | // Initiate the DOM 46 | jsdom(); 47 | 48 | it('should dispatch events',() => { 49 | window.afterglow = { 50 | getPlayer: sinon.spy() 51 | } 52 | 53 | eventbus.players = { 54 | playerid : { 55 | listeners : { 56 | 'event1': [testCallback] 57 | } 58 | } 59 | }; 60 | eventbus.dispatch('playerid','event1'); 61 | sinon.assert.calledOnce(testCallback); 62 | 63 | eventbus.players = { 64 | playerid : { 65 | listeners : { 66 | 'event1': [testCallback, testCallback] 67 | } 68 | } 69 | }; 70 | eventbus.dispatch('playerid','event1'); 71 | sinon.assert.calledThrice(testCallback); 72 | }); 73 | 74 | it('should pass the player instance', () => { 75 | window.afterglow = { 76 | getPlayer: sinon.stub().returns({id:42}) 77 | } 78 | 79 | eventbus.players = { 80 | playerid : { 81 | listeners : { 82 | 'event1': [testCallback] 83 | } 84 | } 85 | }; 86 | eventbus.dispatch('playerid','event1'); 87 | assert(window.afterglow.getPlayer.called); 88 | assert(testCallback.calledWith({type:'event1', player: {id:42}, playerid: 'playerid'})); 89 | }); 90 | }); 91 | 92 | }); -------------------------------------------------------------------------------- /test/test.3.util.js: -------------------------------------------------------------------------------- 1 | import Util from '../src/js/afterglow/lib/Util'; 2 | 3 | var chai = require('chai'); 4 | var sinon = require("sinon"); 5 | var sinonChai = require("sinon-chai"); 6 | var jsdom = require('mocha-jsdom'); 7 | 8 | chai.use(sinonChai); 9 | chai.should(); 10 | 11 | var assert = chai.assert; 12 | var expect = chai.expect; 13 | 14 | describe("Util", () => { 15 | // Initiate the DOM 16 | jsdom(); 17 | 18 | var util, 19 | $; 20 | 21 | beforeEach(() => { 22 | $ = require('jquery'); 23 | util = new Util; 24 | }); 25 | 26 | describe('constructor', () => { 27 | it('should not do anything', () => { 28 | expect(util).to.be.an('object'); 29 | expect(util).to.eql({}); 30 | }); 31 | }); 32 | 33 | describe('isYoutubePlayer()', () => { 34 | it('should properly check if the videoelement is a Youtube player or not', () => { 35 | var videoelement = { 36 | hasAttribute: () => { 37 | return 'somevalue' 38 | } 39 | } 40 | sinon.spy(videoelement, 'hasAttribute'); 41 | var res = util.isYoutubePlayer(videoelement); 42 | expect(videoelement.hasAttribute).to.have.been.calledOnce; 43 | expect(videoelement.hasAttribute).to.have.been.calledWith('data-youtube-id'); 44 | expect(res).to.equal('somevalue'); 45 | }); 46 | }); 47 | 48 | describe('loadYoutubeThumbnailUrl()', () => { 49 | it('should return a youtube file properly', () => { 50 | var res = util.loadYoutubeThumbnailUrl('sometestid'); 51 | expect(res).to.eql('https://img.youtube.com/vi/sometestid/maxresdefault.jpg'); 52 | }); 53 | }); 54 | 55 | describe('isVimeoPlayer()', () => { 56 | it('should properly check if the videoelement is a Vimeo player or not', () => { 57 | var videoelement = { 58 | hasAttribute: () => { 59 | return 'somevalue' 60 | } 61 | } 62 | sinon.spy(videoelement, 'hasAttribute'); 63 | var res = util.isVimeoPlayer(videoelement); 64 | expect(videoelement.hasAttribute).to.have.been.calledOnce; 65 | expect(videoelement.hasAttribute).to.have.been.calledWith('data-vimeo-id'); 66 | expect(res).to.equal('somevalue'); 67 | }); 68 | }); 69 | 70 | describe('isMobile()', () => { 71 | afterEach(() => { 72 | // Prevention for the other tests 73 | navigator.__defineGetter__('userAgent', function(){ 74 | return null // customized user agent 75 | }); 76 | }); 77 | 78 | it('should return false for nothing', () => { 79 | var res = util.isMobile(); 80 | expect(res).to.be.false; 81 | }); 82 | 83 | it('should return false for random string', () => { 84 | navigator.__defineGetter__('userAgent', function(){ 85 | return 'ThisisARandomString98a2z092h ac09ausc' // customized user agent 86 | }); 87 | var res = util.isMobile(); 88 | expect(res).to.be.false; 89 | }); 90 | 91 | it('should return true for Android', () => { 92 | navigator.__defineGetter__('userAgent', function(){ 93 | return 'SomethingAndroidSomeotherThing' // customized user agent 94 | }); 95 | var res = util.isMobile(); 96 | expect(res).to.be.true; 97 | }); 98 | 99 | it('should return true for BlackBerry', () => { 100 | navigator.__defineGetter__('userAgent', function(){ 101 | return 'SomethingBlackBerrySomeotherThing' // customized user agent 102 | }); 103 | var res = util.isMobile(); 104 | expect(res).to.be.true; 105 | }); 106 | 107 | it('should return true for iPhone', () => { 108 | navigator.__defineGetter__('userAgent', function(){ 109 | return 'SomethingiPhoneSomeotherThing' // customized user agent 110 | }); 111 | var res = util.isMobile(); 112 | expect(res).to.be.true; 113 | }); 114 | 115 | it('should return true for iPad', () => { 116 | navigator.__defineGetter__('userAgent', function(){ 117 | return 'SomethingAndroidSomeotherThing' // customized user agent 118 | }); 119 | var res = util.isMobile(); 120 | expect(res).to.be.true; 121 | }); 122 | 123 | it('should return true for iPod', () => { 124 | navigator.__defineGetter__('userAgent', function(){ 125 | return 'SomethingAndroidSomeotherThing' // customized user agent 126 | }); 127 | var res = util.isMobile(); 128 | expect(res).to.be.true; 129 | }); 130 | 131 | it('should return true for OperaMini', () => { 132 | navigator.__defineGetter__('userAgent', function(){ 133 | return 'SomethingAndroidSomeotherThing' // customized user agent 134 | }); 135 | var res = util.isMobile(); 136 | expect(res).to.be.true; 137 | }); 138 | 139 | it('should return true for IEMobile', () => { 140 | navigator.__defineGetter__('userAgent', function(){ 141 | return 'SomethingAndroidSomeotherThing' // customized user agent 142 | }); 143 | var res = util.isMobile(); 144 | expect(res).to.be.true; 145 | }); 146 | }); 147 | 148 | describe('merge_objects()', () => { 149 | it('should merge two objects properly', () => { 150 | var object1 = { test1 : true }; 151 | var object2 = { test2 : true }; 152 | var merged = util.merge_objects(object1, object2); 153 | expect(merged).to.eql({test1 : true, test2: true}); 154 | }); 155 | it('should work with one empty object', () => { 156 | var object1 = { }; 157 | var object2 = { test2 : true }; 158 | var merged = util.merge_objects(object1, object2); 159 | expect(merged).to.eql({test2: true}); 160 | var merged = util.merge_objects(object2, object1); 161 | expect(merged).to.eql({test2: true}); 162 | 163 | }); 164 | }); 165 | 166 | }); -------------------------------------------------------------------------------- /test/test.4.emitter.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var sinon = require("sinon"); 3 | var sinonChai = require("sinon-chai"); 4 | var jsdom = require('mocha-jsdom'); 5 | 6 | chai.use(sinonChai); 7 | chai.should(); 8 | 9 | var assert = chai.assert; 10 | var expect = chai.expect; 11 | 12 | var Emitter = require('../vendor/Emitter/Emitter'); 13 | 14 | function Custom() { 15 | Emitter.call(this) 16 | } 17 | 18 | Custom.prototype.__proto__ = Emitter.prototype; 19 | 20 | describe('Custom', function(){ 21 | describe('with Emitter.call(this)', function(){ 22 | it('should work', function(done){ 23 | var emitter = new Custom; 24 | emitter.on('foo', done); 25 | emitter.emit('foo'); 26 | }) 27 | }) 28 | }) 29 | 30 | describe('Emitter', function(){ 31 | describe('.on(event, fn)', function(){ 32 | it('should add listeners', function(){ 33 | var emitter = new Emitter; 34 | var calls = []; 35 | 36 | emitter.on('foo', function(val){ 37 | calls.push('one', val); 38 | }); 39 | 40 | emitter.on('foo', function(val){ 41 | calls.push('two', val); 42 | }); 43 | 44 | emitter.emit('foo', 1); 45 | emitter.emit('bar', 1); 46 | emitter.emit('foo', 2); 47 | 48 | calls.should.eql([ 'one', 1, 'two', 1, 'one', 2, 'two', 2 ]); 49 | }) 50 | 51 | it('should add listeners for events which are same names with methods of Object.prototype', function(){ 52 | var emitter = new Emitter; 53 | var calls = []; 54 | 55 | emitter.on('constructor', function(val){ 56 | calls.push('one', val); 57 | }); 58 | 59 | emitter.on('__proto__', function(val){ 60 | calls.push('two', val); 61 | }); 62 | 63 | emitter.emit('constructor', 1); 64 | emitter.emit('__proto__', 2); 65 | 66 | calls.should.eql([ 'one', 1, 'two', 2 ]); 67 | }) 68 | }) 69 | 70 | describe('.once(event, fn)', function(){ 71 | it('should add a single-shot listener', function(){ 72 | var emitter = new Emitter; 73 | var calls = []; 74 | 75 | emitter.once('foo', function(val){ 76 | calls.push('one', val); 77 | }); 78 | 79 | emitter.emit('foo', 1); 80 | emitter.emit('foo', 2); 81 | emitter.emit('foo', 3); 82 | emitter.emit('bar', 1); 83 | 84 | calls.should.eql([ 'one', 1 ]); 85 | }) 86 | }) 87 | 88 | describe('.off(event, fn)', function(){ 89 | it('should remove a listener', function(){ 90 | var emitter = new Emitter; 91 | var calls = []; 92 | 93 | function one() { calls.push('one'); } 94 | function two() { calls.push('two'); } 95 | 96 | emitter.on('foo', one); 97 | emitter.on('foo', two); 98 | emitter.off('foo', two); 99 | 100 | emitter.emit('foo'); 101 | 102 | calls.should.eql([ 'one' ]); 103 | }) 104 | 105 | it('should work with .once()', function(){ 106 | var emitter = new Emitter; 107 | var calls = []; 108 | 109 | function one() { calls.push('one'); } 110 | 111 | emitter.once('foo', one); 112 | emitter.once('fee', one); 113 | emitter.off('foo', one); 114 | 115 | emitter.emit('foo'); 116 | 117 | calls.should.eql([]); 118 | }) 119 | 120 | it('should work when called from an event', function(){ 121 | var emitter = new Emitter 122 | , called 123 | function b () { 124 | called = true; 125 | } 126 | emitter.on('tobi', function () { 127 | emitter.off('tobi', b); 128 | }); 129 | emitter.on('tobi', b); 130 | emitter.emit('tobi'); 131 | called.should.be.true; 132 | called = false; 133 | emitter.emit('tobi'); 134 | called.should.be.false; 135 | }); 136 | }) 137 | 138 | describe('.off(event)', function(){ 139 | it('should remove all listeners for an event', function(){ 140 | var emitter = new Emitter; 141 | var calls = []; 142 | 143 | function one() { calls.push('one'); } 144 | function two() { calls.push('two'); } 145 | 146 | emitter.on('foo', one); 147 | emitter.on('foo', two); 148 | emitter.off('foo'); 149 | 150 | emitter.emit('foo'); 151 | emitter.emit('foo'); 152 | 153 | calls.should.eql([]); 154 | }) 155 | }) 156 | 157 | describe('.off()', function(){ 158 | it('should remove all listeners', function(){ 159 | var emitter = new Emitter; 160 | var calls = []; 161 | 162 | function one() { calls.push('one'); } 163 | function two() { calls.push('two'); } 164 | 165 | emitter.on('foo', one); 166 | emitter.on('bar', two); 167 | 168 | emitter.emit('foo'); 169 | emitter.emit('bar'); 170 | 171 | emitter.off(); 172 | 173 | emitter.emit('foo'); 174 | emitter.emit('bar'); 175 | 176 | calls.should.eql(['one', 'two']); 177 | }) 178 | }) 179 | 180 | describe('.listeners(event)', function(){ 181 | describe('when handlers are present', function(){ 182 | it('should return an array of callbacks', function(){ 183 | var emitter = new Emitter; 184 | function foo(){} 185 | emitter.on('foo', foo); 186 | emitter.listeners('foo').should.eql([foo]); 187 | }) 188 | }) 189 | 190 | describe('when no handlers are present', function(){ 191 | it('should return an empty array', function(){ 192 | var emitter = new Emitter; 193 | emitter.listeners('foo').should.eql([]); 194 | }) 195 | }) 196 | }) 197 | 198 | describe('.hasListeners(event)', function(){ 199 | describe('when handlers are present', function(){ 200 | it('should return true', function(){ 201 | var emitter = new Emitter; 202 | emitter.on('foo', function(){}); 203 | emitter.hasListeners('foo').should.be.true; 204 | }) 205 | }) 206 | 207 | describe('when no handlers are present', function(){ 208 | it('should return false', function(){ 209 | var emitter = new Emitter; 210 | emitter.hasListeners('foo').should.be.false; 211 | }) 212 | }) 213 | }) 214 | }) 215 | 216 | describe('Emitter(obj)', function(){ 217 | it('should mixin', function(done){ 218 | var proto = {}; 219 | Emitter(proto); 220 | proto.on('something', done); 221 | proto.emit('something'); 222 | }) 223 | }) -------------------------------------------------------------------------------- /vendor/Emitter/Emitter.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Expose `Emitter`. 4 | */ 5 | 6 | module.exports = Emitter; 7 | 8 | /** 9 | * Initialize a new `Emitter`. 10 | * 11 | * @api public 12 | */ 13 | 14 | function Emitter(obj) { 15 | if (obj) return mixin(obj); 16 | }; 17 | 18 | /** 19 | * Mixin the emitter properties. 20 | * 21 | * @param {Object} obj 22 | * @return {Object} 23 | * @api private 24 | */ 25 | 26 | function mixin(obj) { 27 | for (var key in Emitter.prototype) { 28 | obj[key] = Emitter.prototype[key]; 29 | } 30 | return obj; 31 | } 32 | 33 | /** 34 | * Listen on the given `event` with `fn`. 35 | * 36 | * @param {String} event 37 | * @param {Function} fn 38 | * @return {Emitter} 39 | * @api public 40 | */ 41 | 42 | Emitter.prototype.on = 43 | Emitter.prototype.addEventListener = function(event, fn){ 44 | this._callbacks = this._callbacks || {}; 45 | (this._callbacks['$' + event] = this._callbacks['$' + event] || []) 46 | .push(fn); 47 | return this; 48 | }; 49 | 50 | /** 51 | * Adds an `event` listener that will be invoked a single 52 | * time then automatically removed. 53 | * 54 | * @param {String} event 55 | * @param {Function} fn 56 | * @return {Emitter} 57 | * @api public 58 | */ 59 | 60 | Emitter.prototype.once = function(event, fn){ 61 | function on() { 62 | this.off(event, on); 63 | fn.apply(this, arguments); 64 | } 65 | 66 | on.fn = fn; 67 | this.on(event, on); 68 | return this; 69 | }; 70 | 71 | /** 72 | * Remove the given callback for `event` or all 73 | * registered callbacks. 74 | * 75 | * @param {String} event 76 | * @param {Function} fn 77 | * @return {Emitter} 78 | * @api public 79 | */ 80 | 81 | Emitter.prototype.off = 82 | Emitter.prototype.removeListener = 83 | Emitter.prototype.removeAllListeners = 84 | Emitter.prototype.removeEventListener = function(event, fn){ 85 | this._callbacks = this._callbacks || {}; 86 | 87 | // all 88 | if (0 == arguments.length) { 89 | this._callbacks = {}; 90 | return this; 91 | } 92 | 93 | // specific event 94 | var callbacks = this._callbacks['$' + event]; 95 | if (!callbacks) return this; 96 | 97 | // remove all handlers 98 | if (1 == arguments.length) { 99 | delete this._callbacks['$' + event]; 100 | return this; 101 | } 102 | 103 | // remove specific handler 104 | var cb; 105 | for (var i = 0; i < callbacks.length; i++) { 106 | cb = callbacks[i]; 107 | if (cb === fn || cb.fn === fn) { 108 | callbacks.splice(i, 1); 109 | break; 110 | } 111 | } 112 | return this; 113 | }; 114 | 115 | /** 116 | * Emit `event` with the given args. 117 | * 118 | * @param {String} event 119 | * @param {Mixed} ... 120 | * @return {Emitter} 121 | */ 122 | 123 | Emitter.prototype.emit = function(event){ 124 | this._callbacks = this._callbacks || {}; 125 | var args = [].slice.call(arguments, 1) 126 | , callbacks = this._callbacks['$' + event]; 127 | 128 | if (callbacks) { 129 | callbacks = callbacks.slice(0); 130 | for (var i = 0, len = callbacks.length; i < len; ++i) { 131 | callbacks[i].apply(this, args); 132 | } 133 | } 134 | 135 | return this; 136 | }; 137 | 138 | /** 139 | * Return array of callbacks for `event`. 140 | * 141 | * @param {String} event 142 | * @return {Array} 143 | * @api public 144 | */ 145 | 146 | Emitter.prototype.listeners = function(event){ 147 | this._callbacks = this._callbacks || {}; 148 | return this._callbacks['$' + event] || []; 149 | }; 150 | 151 | /** 152 | * Check if this emitter has `event` handlers. 153 | * 154 | * @param {String} event 155 | * @return {Boolean} 156 | * @api public 157 | */ 158 | 159 | Emitter.prototype.hasListeners = function(event){ 160 | return !! this.listeners(event).length; 161 | }; 162 | -------------------------------------------------------------------------------- /vendor/videojs/plugins/Youtube.js: -------------------------------------------------------------------------------- 1 | /* The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 Benoit Tremblay 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. */ 22 | /*global define, YT*/ 23 | (function (root, factory) { 24 | if(typeof exports==='object' && typeof module!=='undefined') { 25 | module.exports = factory(require('video.js')); 26 | } else if(typeof define === 'function' && define.amd) { 27 | define(['videojs'], function(videojs){ 28 | return (root.Youtube = factory(videojs)); 29 | }); 30 | } else { 31 | root.Youtube = factory(root.videojs); 32 | } 33 | }(this, function(videojs) { 34 | 'use strict'; 35 | 36 | var _isOnMobile = videojs.browser.IS_IOS || videojs.browser.IS_ANDROID; 37 | var Tech = videojs.getTech('Tech'); 38 | 39 | var Youtube = videojs.extend(Tech, { 40 | 41 | constructor: function(options, ready) { 42 | loadScript('https://www.youtube.com/iframe_api', apiLoaded); 43 | 44 | Tech.call(this, options, ready); 45 | 46 | this.setPoster(options.poster); 47 | this.setSrc(this.options_.source, true); 48 | 49 | // Set the vjs-youtube class to the player 50 | // Parent is not set yet so we have to wait a tick 51 | this.setTimeout(function() { 52 | if (this.el_) { 53 | this.el_.parentNode.className += ' vjs-youtube'; 54 | 55 | if (_isOnMobile) { 56 | this.el_.parentNode.className += ' vjs-youtube-mobile'; 57 | } 58 | 59 | if (Youtube.isApiReady) { 60 | this.initYTPlayer(); 61 | } else { 62 | Youtube.apiReadyQueue.push(this); 63 | } 64 | } 65 | }.bind(this)); 66 | }, 67 | 68 | dispose: function() { 69 | if (this.ytPlayer) { 70 | //Dispose of the YouTube Player 71 | if (this.ytPlayer.stopVideo) { 72 | this.ytPlayer.stopVideo(); 73 | } 74 | if (this.ytPlayer.destroy) { 75 | this.ytPlayer.destroy(); 76 | } 77 | } else { 78 | //YouTube API hasn't finished loading or the player is already disposed 79 | var index = Youtube.apiReadyQueue.indexOf(this); 80 | if (index !== -1) { 81 | Youtube.apiReadyQueue.splice(index, 1); 82 | } 83 | } 84 | this.ytPlayer = null; 85 | 86 | this.el_.parentNode.className = this.el_.parentNode.className 87 | .replace(' vjs-youtube', '') 88 | .replace(' vjs-youtube-mobile', ''); 89 | this.el_.parentNode.removeChild(this.el_); 90 | 91 | //Needs to be called after the YouTube player is destroyed, otherwise there will be a null reference exception 92 | Tech.prototype.dispose.call(this); 93 | }, 94 | 95 | createEl: function() { 96 | var div = document.createElement('div'); 97 | div.setAttribute('id', this.options_.techId); 98 | div.setAttribute('style', 'width:100%;height:100%;top:0;left:0;position:absolute'); 99 | div.setAttribute('class', 'vjs-tech'); 100 | 101 | var divWrapper = document.createElement('div'); 102 | divWrapper.appendChild(div); 103 | 104 | if (!_isOnMobile && !this.options_.ytControls) { 105 | var divBlocker = document.createElement('div'); 106 | divBlocker.setAttribute('class', 'vjs-iframe-blocker'); 107 | divBlocker.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%'); 108 | 109 | // In case the blocker is still there and we want to pause 110 | divBlocker.onclick = function() { 111 | this.pause(); 112 | }.bind(this); 113 | 114 | divWrapper.appendChild(divBlocker); 115 | } 116 | 117 | return divWrapper; 118 | }, 119 | 120 | initYTPlayer: function() { 121 | var playerVars = { 122 | controls: 0, 123 | modestbranding: 1, 124 | rel: 0, 125 | showinfo: 0, 126 | loop: this.options_.loop ? 1 : 0 127 | }; 128 | 129 | // Let the user set any YouTube parameter 130 | // https://developers.google.com/youtube/player_parameters?playerVersion=HTML5#Parameters 131 | // To use YouTube controls, you must use ytControls instead 132 | // To use the loop or autoplay, use the video.js settings 133 | 134 | if (typeof this.options_.autohide !== 'undefined') { 135 | playerVars.autohide = this.options_.autohide; 136 | } 137 | 138 | if (typeof this.options_['cc_load_policy'] !== 'undefined') { 139 | playerVars['cc_load_policy'] = this.options_['cc_load_policy']; 140 | } 141 | 142 | if (typeof this.options_.ytControls !== 'undefined') { 143 | playerVars.controls = this.options_.ytControls; 144 | } 145 | 146 | if (typeof this.options_.disablekb !== 'undefined') { 147 | playerVars.disablekb = this.options_.disablekb; 148 | } 149 | 150 | if (typeof this.options_.end !== 'undefined') { 151 | playerVars.end = this.options_.end; 152 | } 153 | 154 | if (typeof this.options_.color !== 'undefined') { 155 | playerVars.color = this.options_.color; 156 | } 157 | 158 | if (!playerVars.controls) { 159 | // Let video.js handle the fullscreen unless it is the YouTube native controls 160 | playerVars.fs = 0; 161 | } else if (typeof this.options_.fs !== 'undefined') { 162 | playerVars.fs = this.options_.fs; 163 | } 164 | 165 | if (typeof this.options_.end !== 'undefined') { 166 | playerVars.end = this.options_.end; 167 | } 168 | 169 | if (typeof this.options_.hl !== 'undefined') { 170 | playerVars.hl = this.options_.hl; 171 | } else if (typeof this.options_.language !== 'undefined') { 172 | // Set the YouTube player on the same language than video.js 173 | playerVars.hl = this.options_.language.substr(0, 2); 174 | } 175 | 176 | if (typeof this.options_['iv_load_policy'] !== 'undefined') { 177 | playerVars['iv_load_policy'] = this.options_['iv_load_policy']; 178 | } 179 | 180 | if (typeof this.options_.list !== 'undefined') { 181 | playerVars.list = this.options_.list; 182 | } else if (this.url && typeof this.url.listId !== 'undefined') { 183 | playerVars.list = this.url.listId; 184 | } 185 | 186 | if (typeof this.options_.listType !== 'undefined') { 187 | playerVars.listType = this.options_.listType; 188 | } 189 | 190 | if (typeof this.options_.modestbranding !== 'undefined') { 191 | playerVars.modestbranding = this.options_.modestbranding; 192 | } 193 | 194 | if (typeof this.options_.playlist !== 'undefined') { 195 | playerVars.playlist = this.options_.playlist; 196 | } 197 | 198 | if (typeof this.options_.playsinline !== 'undefined') { 199 | playerVars.playsinline = this.options_.playsinline; 200 | } 201 | 202 | if (typeof this.options_.rel !== 'undefined') { 203 | playerVars.rel = this.options_.rel; 204 | } 205 | 206 | if (typeof this.options_.showinfo !== 'undefined') { 207 | playerVars.showinfo = this.options_.showinfo; 208 | } 209 | 210 | if (typeof this.options_.start !== 'undefined') { 211 | playerVars.start = this.options_.start; 212 | } 213 | 214 | if (typeof this.options_.theme !== 'undefined') { 215 | playerVars.theme = this.options_.theme; 216 | } 217 | 218 | // Allow undocumented options to be passed along via customVars 219 | if (typeof this.options_.customVars !== 'undefined') { 220 | var customVars = this.options_.customVars; 221 | Object.keys(customVars).forEach(function(key) { 222 | playerVars[key] = customVars[key]; 223 | }); 224 | } 225 | 226 | this.activeVideoId = this.url ? this.url.videoId : null; 227 | this.activeList = playerVars.list; 228 | 229 | this.ytPlayer = new YT.Player(this.options_.techId, { 230 | videoId: this.activeVideoId, 231 | playerVars: playerVars, 232 | events: { 233 | onReady: this.onPlayerReady.bind(this), 234 | onPlaybackQualityChange: this.onPlayerPlaybackQualityChange.bind(this), 235 | onPlaybackRateChange: this.onPlayerPlaybackRateChange.bind(this), 236 | onStateChange: this.onPlayerStateChange.bind(this), 237 | onVolumeChange: this.onPlayerVolumeChange.bind(this), 238 | onError: this.onPlayerError.bind(this) 239 | } 240 | }); 241 | }, 242 | 243 | onPlayerReady: function() { 244 | if (this.options_.muted) { 245 | this.ytPlayer.mute(); 246 | } 247 | 248 | var playbackRates = this.ytPlayer.getAvailablePlaybackRates(); 249 | if (playbackRates.length > 1) { 250 | this.featuresPlaybackRate = true; 251 | } 252 | 253 | this.playerReady_ = true; 254 | this.triggerReady(); 255 | 256 | if (this.playOnReady) { 257 | this.play(); 258 | } else if (this.cueOnReady) { 259 | this.cueVideoById_(this.url.videoId); 260 | this.activeVideoId = this.url.videoId; 261 | } 262 | }, 263 | 264 | onPlayerPlaybackQualityChange: function() { 265 | 266 | }, 267 | 268 | onPlayerPlaybackRateChange: function() { 269 | this.trigger('ratechange'); 270 | }, 271 | 272 | onPlayerStateChange: function(e) { 273 | var state = e.data; 274 | 275 | if (state === this.lastState || this.errorNumber) { 276 | return; 277 | } 278 | 279 | this.lastState = state; 280 | 281 | switch (state) { 282 | case -1: 283 | this.trigger('loadstart'); 284 | this.trigger('loadedmetadata'); 285 | this.trigger('durationchange'); 286 | this.trigger('ratechange'); 287 | break; 288 | 289 | case YT.PlayerState.ENDED: 290 | this.trigger('ended'); 291 | break; 292 | 293 | case YT.PlayerState.PLAYING: 294 | this.trigger('timeupdate'); 295 | this.trigger('durationchange'); 296 | this.trigger('playing'); 297 | this.trigger('play'); 298 | 299 | if (this.isSeeking) { 300 | this.onSeeked(); 301 | } 302 | break; 303 | 304 | case YT.PlayerState.PAUSED: 305 | this.trigger('canplay'); 306 | if (this.isSeeking) { 307 | this.onSeeked(); 308 | } else { 309 | this.trigger('pause'); 310 | } 311 | break; 312 | 313 | case YT.PlayerState.BUFFERING: 314 | this.player_.trigger('timeupdate'); 315 | this.player_.trigger('waiting'); 316 | break; 317 | } 318 | }, 319 | 320 | onPlayerVolumeChange: function() { 321 | this.trigger('volumechange'); 322 | }, 323 | 324 | onPlayerError: function(e) { 325 | this.errorNumber = e.data; 326 | this.trigger('pause'); 327 | this.trigger('error'); 328 | }, 329 | 330 | error: function() { 331 | var code = 1000 + this.errorNumber; // as smaller codes are reserved 332 | switch (this.errorNumber) { 333 | case 5: 334 | return { code: code, message: 'Error while trying to play the video' }; 335 | 336 | case 2: 337 | case 100: 338 | return { code: code, message: 'Unable to find the video' }; 339 | 340 | case 101: 341 | case 150: 342 | return { 343 | code: code, 344 | message: 'Playback on other Websites has been disabled by the video owner.' 345 | }; 346 | } 347 | 348 | return { code: code, message: 'YouTube unknown error (' + this.errorNumber + ')' }; 349 | }, 350 | 351 | loadVideoById_: function(id) { 352 | var options = { 353 | videoId: id 354 | }; 355 | if (this.options_.start) { 356 | options.startSeconds = this.options_.start; 357 | } 358 | if (this.options_.end) { 359 | options.endEnd = this.options_.end; 360 | } 361 | this.ytPlayer.loadVideoById(options); 362 | }, 363 | 364 | cueVideoById_: function(id) { 365 | var options = { 366 | videoId: id 367 | }; 368 | if (this.options_.start) { 369 | options.startSeconds = this.options_.start; 370 | } 371 | if (this.options_.end) { 372 | options.endEnd = this.options_.end; 373 | } 374 | this.ytPlayer.cueVideoById(options); 375 | }, 376 | 377 | src: function(src) { 378 | if (src) { 379 | this.setSrc({ src: src }); 380 | } 381 | 382 | return this.source; 383 | }, 384 | 385 | poster: function() { 386 | // You can't start programmaticlly a video with a mobile 387 | // through the iframe so we hide the poster and the play button (with CSS) 388 | if (_isOnMobile) { 389 | return null; 390 | } 391 | 392 | return this.poster_; 393 | }, 394 | 395 | setPoster: function(poster) { 396 | this.poster_ = poster; 397 | }, 398 | 399 | setSrc: function(source) { 400 | if (!source || !source.src) { 401 | return; 402 | } 403 | 404 | delete this.errorNumber; 405 | this.source = source; 406 | this.url = Youtube.parseUrl(source.src); 407 | 408 | if (!this.options_.poster) { 409 | if (this.url.videoId) { 410 | // Set the low resolution first 411 | this.poster_ = 'https://img.youtube.com/vi/' + this.url.videoId + '/0.jpg'; 412 | this.trigger('posterchange'); 413 | 414 | // Check if their is a high res 415 | this.checkHighResPoster(); 416 | } 417 | } 418 | 419 | if (this.options_.autoplay && !_isOnMobile) { 420 | if (this.isReady_) { 421 | this.play(); 422 | } else { 423 | this.playOnReady = true; 424 | } 425 | } else if (this.activeVideoId !== this.url.videoId) { 426 | if (this.isReady_) { 427 | this.cueVideoById_(this.url.videoId); 428 | this.activeVideoId = this.url.videoId; 429 | } else { 430 | this.cueOnReady = true; 431 | } 432 | } 433 | }, 434 | 435 | autoplay: function() { 436 | return this.options_.autoplay; 437 | }, 438 | 439 | setAutoplay: function(val) { 440 | this.options_.autoplay = val; 441 | }, 442 | 443 | loop: function() { 444 | return this.options_.loop; 445 | }, 446 | 447 | setLoop: function(val) { 448 | this.options_.loop = val; 449 | }, 450 | 451 | play: function() { 452 | if (!this.url || !this.url.videoId) { 453 | return; 454 | } 455 | 456 | this.wasPausedBeforeSeek = false; 457 | 458 | if (this.isReady_) { 459 | if (this.url.listId) { 460 | if (this.activeList === this.url.listId) { 461 | this.ytPlayer.playVideo(); 462 | } else { 463 | this.ytPlayer.loadPlaylist(this.url.listId); 464 | this.activeList = this.url.listId; 465 | } 466 | } 467 | 468 | if (this.activeVideoId === this.url.videoId) { 469 | this.ytPlayer.playVideo(); 470 | } else { 471 | this.loadVideoById_(this.url.videoId); 472 | this.activeVideoId = this.url.videoId; 473 | } 474 | } else { 475 | this.trigger('waiting'); 476 | this.playOnReady = true; 477 | } 478 | }, 479 | 480 | pause: function() { 481 | if (this.ytPlayer) { 482 | this.ytPlayer.pauseVideo(); 483 | } 484 | }, 485 | 486 | paused: function() { 487 | return (this.ytPlayer) ? 488 | (this.lastState !== YT.PlayerState.PLAYING && this.lastState !== YT.PlayerState.BUFFERING) 489 | : true; 490 | }, 491 | 492 | currentTime: function() { 493 | return this.ytPlayer ? this.ytPlayer.getCurrentTime() : 0; 494 | }, 495 | 496 | setCurrentTime: function(seconds) { 497 | if (this.lastState === YT.PlayerState.PAUSED) { 498 | this.timeBeforeSeek = this.currentTime(); 499 | } 500 | 501 | if (!this.isSeeking) { 502 | this.wasPausedBeforeSeek = this.paused(); 503 | } 504 | 505 | this.ytPlayer.seekTo(seconds, true); 506 | this.trigger('timeupdate'); 507 | this.trigger('seeking'); 508 | this.isSeeking = true; 509 | 510 | // A seek event during pause does not return an event to trigger a seeked event, 511 | // so run an interval timer to look for the currentTime to change 512 | if (this.lastState === YT.PlayerState.PAUSED && this.timeBeforeSeek !== seconds) { 513 | clearInterval(this.checkSeekedInPauseInterval); 514 | this.checkSeekedInPauseInterval = setInterval(function() { 515 | if (this.lastState !== YT.PlayerState.PAUSED || !this.isSeeking) { 516 | // If something changed while we were waiting for the currentTime to change, 517 | // clear the interval timer 518 | clearInterval(this.checkSeekedInPauseInterval); 519 | } else if (this.currentTime() !== this.timeBeforeSeek) { 520 | this.trigger('timeupdate'); 521 | this.onSeeked(); 522 | } 523 | }.bind(this), 250); 524 | } 525 | }, 526 | 527 | seeking: function () { 528 | return this.isSeeking; 529 | }, 530 | 531 | seekable: function () { 532 | if(!this.ytPlayer) { 533 | return videojs.createTimeRange(); 534 | } 535 | 536 | return videojs.createTimeRange(0, this.ytPlayer.getDuration()); 537 | }, 538 | 539 | onSeeked: function() { 540 | clearInterval(this.checkSeekedInPauseInterval); 541 | this.isSeeking = false; 542 | 543 | if (this.wasPausedBeforeSeek) { 544 | this.pause(); 545 | } 546 | 547 | this.trigger('seeked'); 548 | }, 549 | 550 | playbackRate: function() { 551 | return this.ytPlayer ? this.ytPlayer.getPlaybackRate() : 1; 552 | }, 553 | 554 | setPlaybackRate: function(suggestedRate) { 555 | if (!this.ytPlayer) { 556 | return; 557 | } 558 | 559 | this.ytPlayer.setPlaybackRate(suggestedRate); 560 | }, 561 | 562 | duration: function() { 563 | return this.ytPlayer ? this.ytPlayer.getDuration() : 0; 564 | }, 565 | 566 | currentSrc: function() { 567 | return this.source && this.source.src; 568 | }, 569 | 570 | ended: function() { 571 | return this.ytPlayer ? (this.lastState === YT.PlayerState.ENDED) : false; 572 | }, 573 | 574 | volume: function() { 575 | return this.ytPlayer ? this.ytPlayer.getVolume() / 100.0 : 1; 576 | }, 577 | 578 | setVolume: function(percentAsDecimal) { 579 | if (!this.ytPlayer) { 580 | return; 581 | } 582 | 583 | this.ytPlayer.setVolume(percentAsDecimal * 100.0); 584 | }, 585 | 586 | muted: function() { 587 | return this.ytPlayer ? this.ytPlayer.isMuted() : false; 588 | }, 589 | 590 | setMuted: function(mute) { 591 | if (!this.ytPlayer) { 592 | return; 593 | } 594 | else{ 595 | this.muted(true); 596 | } 597 | 598 | if (mute) { 599 | this.ytPlayer.mute(); 600 | } else { 601 | this.ytPlayer.unMute(); 602 | } 603 | this.setTimeout( function(){ 604 | this.trigger('volumechange'); 605 | }, 50); 606 | }, 607 | 608 | buffered: function() { 609 | if(!this.ytPlayer || !this.ytPlayer.getVideoLoadedFraction) { 610 | return videojs.createTimeRange(); 611 | } 612 | 613 | var bufferedEnd = this.ytPlayer.getVideoLoadedFraction() * this.ytPlayer.getDuration(); 614 | 615 | return videojs.createTimeRange(0, bufferedEnd); 616 | }, 617 | 618 | // TODO: Can we really do something with this on YouTUbe? 619 | preload: function() {}, 620 | load: function() {}, 621 | reset: function() {}, 622 | 623 | supportsFullScreen: function() { 624 | return true; 625 | }, 626 | 627 | // Tries to get the highest resolution thumbnail available for the video 628 | checkHighResPoster: function(){ 629 | var uri = 'https://img.youtube.com/vi/' + this.url.videoId + '/maxresdefault.jpg'; 630 | 631 | try { 632 | var image = new Image(); 633 | image.onload = function(){ 634 | // Onload may still be called if YouTube returns the 120x90 error thumbnail 635 | if('naturalHeight' in image){ 636 | if (image.naturalHeight <= 90 || image.naturalWidth <= 120) { 637 | return; 638 | } 639 | } else if(image.height <= 90 || image.width <= 120) { 640 | return; 641 | } 642 | 643 | this.poster_ = uri; 644 | this.trigger('posterchange'); 645 | }.bind(this); 646 | image.onerror = function(){}; 647 | image.src = uri; 648 | } 649 | catch(e){} 650 | } 651 | }); 652 | 653 | Youtube.isSupported = function() { 654 | return true; 655 | }; 656 | 657 | Youtube.canPlaySource = function(e) { 658 | return Youtube.canPlayType(e.type); 659 | }; 660 | 661 | Youtube.canPlayType = function(e) { 662 | return (e === 'video/youtube'); 663 | }; 664 | 665 | Youtube.parseUrl = function(url) { 666 | var result = { 667 | videoId: null 668 | }; 669 | 670 | var regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; 671 | var match = url.match(regex); 672 | 673 | if (match && match[2].length === 11) { 674 | result.videoId = match[2]; 675 | } 676 | 677 | var regPlaylist = /[?&]list=([^#\&\?]+)/; 678 | match = url.match(regPlaylist); 679 | 680 | if(match && match[1]) { 681 | result.listId = match[1]; 682 | } 683 | 684 | return result; 685 | }; 686 | 687 | function apiLoaded() { 688 | YT.ready(function() { 689 | Youtube.isApiReady = true; 690 | 691 | for (var i = 0; i < Youtube.apiReadyQueue.length; ++i) { 692 | Youtube.apiReadyQueue[i].initYTPlayer(); 693 | } 694 | }); 695 | } 696 | 697 | function loadScript(src, callback) { 698 | var loaded = false; 699 | var tag = document.createElement('script'); 700 | var firstScriptTag = document.getElementsByTagName('script')[0]; 701 | firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 702 | tag.onload = function () { 703 | if (!loaded) { 704 | loaded = true; 705 | callback(); 706 | } 707 | }; 708 | tag.onreadystatechange = function () { 709 | if (!loaded && (this.readyState === 'complete' || this.readyState === 'loaded')) { 710 | loaded = true; 711 | callback(); 712 | } 713 | }; 714 | tag.src = src; 715 | } 716 | 717 | function injectCss() { 718 | var css = // iframe blocker to catch mouse events 719 | '.vjs-youtube .vjs-iframe-blocker { display: none; }' + 720 | '.vjs-youtube.vjs-user-inactive .vjs-iframe-blocker { display: block; }' + 721 | '.vjs-youtube .vjs-poster { background-size: cover; }' + 722 | '.vjs-youtube-mobile .vjs-big-play-button { display: none; }'; 723 | 724 | var head = document.head || document.getElementsByTagName('head')[0]; 725 | 726 | var style = document.createElement('style'); 727 | style.type = 'text/css'; 728 | 729 | if (style.styleSheet){ 730 | style.styleSheet.cssText = css; 731 | } else { 732 | style.appendChild(document.createTextNode(css)); 733 | } 734 | 735 | head.appendChild(style); 736 | } 737 | 738 | Youtube.apiReadyQueue = []; 739 | 740 | if (typeof document !== 'undefined'){ 741 | injectCss(); 742 | } 743 | 744 | // Older versions of VJS5 doesn't have the registerTech function 745 | if (typeof videojs.registerTech !== 'undefined') { 746 | videojs.registerTech('Youtube', Youtube); 747 | } else { 748 | videojs.registerComponent('Youtube', Youtube); 749 | } 750 | })); -------------------------------------------------------------------------------- /vendor/videojs/plugins/videojs.hotkeys.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Video.js Hotkeys 3 | * https://github.com/ctd1500/videojs-hotkeys 4 | * 5 | * Copyright (c) 2015 Chris Dougherty 6 | * Licensed under the Apache-2.0 license. 7 | */ 8 | 9 | ;(function(root, factory) { 10 | if (typeof define === 'function' && define.amd) { 11 | define([], factory.bind(this, root, root.videojs)); 12 | } else if (typeof module !== 'undefined' && module.exports) { 13 | module.exports = factory(root, root.videojs); 14 | } else { 15 | factory(root, root.videojs); 16 | } 17 | 18 | })(window, function(window, videojs) { 19 | "use strict"; 20 | window['videojs_hotkeys'] = { version: "0.2.17" }; 21 | 22 | var hotkeys = function(options) { 23 | var player = this; 24 | var pEl = player.el(); 25 | var doc = document; 26 | var def_options = { 27 | volumeStep: 0.1, 28 | seekStep: 5, 29 | enableMute: true, 30 | enableVolumeScroll: true, 31 | enableFullscreen: true, 32 | enableNumbers: true, 33 | enableJogStyle: false, 34 | alwaysCaptureHotkeys: false, 35 | enableModifiersForNumbers: true, 36 | playPauseKey: playPauseKey, 37 | rewindKey: rewindKey, 38 | forwardKey: forwardKey, 39 | volumeUpKey: volumeUpKey, 40 | volumeDownKey: volumeDownKey, 41 | muteKey: muteKey, 42 | fullscreenKey: fullscreenKey, 43 | customKeys: {} 44 | }; 45 | 46 | var cPlay = 1, 47 | cRewind = 2, 48 | cForward = 3, 49 | cVolumeUp = 4, 50 | cVolumeDown = 5, 51 | cMute = 6, 52 | cFullscreen = 7; 53 | 54 | // Use built-in merge function from Video.js v5.0+ or v4.4.0+ 55 | var mergeOptions = videojs.mergeOptions || videojs.util.mergeOptions; 56 | options = mergeOptions(def_options, options || {}); 57 | 58 | var volumeStep = options.volumeStep, 59 | seekStep = options.seekStep, 60 | enableMute = options.enableMute, 61 | enableVolumeScroll = options.enableVolumeScroll, 62 | enableFull = options.enableFullscreen, 63 | enableNumbers = options.enableNumbers, 64 | enableJogStyle = options.enableJogStyle, 65 | alwaysCaptureHotkeys = options.alwaysCaptureHotkeys, 66 | enableModifiersForNumbers = options.enableModifiersForNumbers; 67 | 68 | // Set default player tabindex to handle keydown and doubleclick events 69 | if (!pEl.hasAttribute('tabIndex')) { 70 | pEl.setAttribute('tabIndex', '-1'); 71 | } 72 | 73 | // Remove player outline to fix video performance issue 74 | pEl.style.outline = "none"; 75 | 76 | if (alwaysCaptureHotkeys || !player.options_.autoplay) { 77 | player.one('play', function() { 78 | pEl.focus(); // Fixes the .vjs-big-play-button handing focus back to body instead of the player 79 | }); 80 | } 81 | 82 | player.on('play', function() { 83 | // Fix allowing the YouTube plugin to have hotkey support. 84 | var ifblocker = pEl.querySelector('.iframeblocker'); 85 | if (ifblocker && ifblocker.style.display === '') { 86 | ifblocker.style.display = "block"; 87 | ifblocker.style.bottom = "39px"; 88 | } 89 | }); 90 | 91 | var keyDown = function keyDown(event) { 92 | var ewhich = event.which, curTime; 93 | var ePreventDefault = event.preventDefault; 94 | // When controls are disabled, hotkeys will be disabled as well 95 | if (player.controls()) { 96 | 97 | // Don't catch keys if any control buttons are focused, unless alwaysCaptureHotkeys is true 98 | var activeEl = doc.activeElement; 99 | if (alwaysCaptureHotkeys || 100 | activeEl == pEl || 101 | activeEl == pEl.querySelector('.vjs-tech') || 102 | activeEl == pEl.querySelector('.vjs-control-bar') || 103 | activeEl == pEl.querySelector('.iframeblocker')) { 104 | 105 | switch (checkKeys(event, player)) { 106 | // Spacebar toggles play/pause 107 | case cPlay: 108 | ePreventDefault(); 109 | if (alwaysCaptureHotkeys) { 110 | // Prevent control activation with space 111 | event.stopPropagation(); 112 | } 113 | 114 | if (player.paused()) { 115 | player.play(); 116 | } else { 117 | player.pause(); 118 | } 119 | break; 120 | 121 | // Seeking with the left/right arrow keys 122 | case cRewind: // Seek Backward 123 | ePreventDefault(); 124 | curTime = player.currentTime() - seekStep; 125 | // The flash player tech will allow you to seek into negative 126 | // numbers and break the seekbar, so try to prevent that. 127 | if (player.currentTime() <= seekStep) { 128 | curTime = 0; 129 | } 130 | player.currentTime(curTime); 131 | break; 132 | case cForward: // Seek Forward 133 | ePreventDefault(); 134 | player.currentTime(player.currentTime() + seekStep); 135 | break; 136 | 137 | // Volume control with the up/down arrow keys 138 | case cVolumeDown: 139 | ePreventDefault(); 140 | if (!enableJogStyle) { 141 | player.volume(player.volume() - volumeStep); 142 | } else { 143 | curTime = player.currentTime() - 1; 144 | if (player.currentTime() <= 1) { 145 | curTime = 0; 146 | } 147 | player.currentTime(curTime); 148 | } 149 | break; 150 | case cVolumeUp: 151 | ePreventDefault(); 152 | if (!enableJogStyle) { 153 | player.volume(player.volume() + volumeStep); 154 | } else { 155 | player.currentTime(player.currentTime() + 1); 156 | } 157 | break; 158 | 159 | // Toggle Mute with the M key 160 | case cMute: 161 | if (enableMute) { 162 | player.muted(!player.muted()); 163 | } 164 | break; 165 | 166 | // Toggle Fullscreen with the F key 167 | case cFullscreen: 168 | if (enableFull) { 169 | if (player.isFullscreen()) { 170 | player.exitFullscreen(); 171 | } else { 172 | player.requestFullscreen(); 173 | } 174 | } 175 | break; 176 | 177 | default: 178 | // Number keys from 0-9 skip to a percentage of the video. 0 is 0% and 9 is 90% 179 | if ((ewhich > 47 && ewhich < 59) || (ewhich > 95 && ewhich < 106)) { 180 | // Do not handle if enableModifiersForNumbers set to false and keys are Ctrl, Cmd or Alt 181 | if (enableModifiersForNumbers || !(event.metaKey || event.ctrlKey || event.altKey)) { 182 | if (enableNumbers) { 183 | var sub = 48; 184 | if (ewhich > 95) { 185 | sub = 96; 186 | } 187 | var number = ewhich - sub; 188 | ePreventDefault(); 189 | player.currentTime(player.duration() * number * 0.1); 190 | } 191 | } 192 | } 193 | 194 | // Handle any custom hotkeys 195 | for (var customKey in options.customKeys) { 196 | var customHotkey = options.customKeys[customKey]; 197 | // Check for well formed custom keys 198 | if (customHotkey && customHotkey.key && customHotkey.handler) { 199 | // Check if the custom key's condition matches 200 | if (customHotkey.key(event)) { 201 | ePreventDefault(); 202 | customHotkey.handler(player, options, event); 203 | } 204 | } 205 | } 206 | } 207 | } 208 | } 209 | }; 210 | 211 | var doubleClick = function doubleClick(event) { 212 | // When controls are disabled, hotkeys will be disabled as well 213 | if (player.controls()) { 214 | 215 | // Don't catch clicks if any control buttons are focused 216 | var activeEl = event.relatedTarget || event.toElement || doc.activeElement; 217 | if (activeEl == pEl || 218 | activeEl == pEl.querySelector('.vjs-tech') || 219 | activeEl == pEl.querySelector('.iframeblocker')) { 220 | 221 | if (enableFull) { 222 | if (player.isFullscreen()) { 223 | player.exitFullscreen(); 224 | } else { 225 | player.requestFullscreen(); 226 | } 227 | } 228 | } 229 | } 230 | }; 231 | 232 | var mouseScroll = function mouseScroll(event) { 233 | // When controls are disabled, hotkeys will be disabled as well 234 | if (player.controls()) { 235 | var activeEl = event.relatedTarget || event.toElement || doc.activeElement; 236 | if (alwaysCaptureHotkeys || 237 | activeEl == pEl || 238 | activeEl == pEl.querySelector('.vjs-tech') || 239 | activeEl == pEl.querySelector('.iframeblocker') || 240 | activeEl == pEl.querySelector('.vjs-control-bar')) { 241 | 242 | if (enableVolumeScroll) { 243 | event = window.event || event; 244 | var delta = Math.max(-1, Math.min(1, (event.wheelDelta || -event.detail))); 245 | event.preventDefault(); 246 | 247 | if (delta == 1) { 248 | player.volume(player.volume() + volumeStep); 249 | } else if (delta == -1) { 250 | player.volume(player.volume() - volumeStep); 251 | } 252 | } 253 | } 254 | } 255 | }; 256 | 257 | var checkKeys = function checkKeys(e, player) { 258 | // Allow some modularity in defining custom hotkeys 259 | 260 | // Play/Pause check 261 | if (options.playPauseKey(e, player)) { 262 | return cPlay; 263 | } 264 | 265 | // Seek Backward check 266 | if (options.rewindKey(e, player)) { 267 | return cRewind; 268 | } 269 | 270 | // Seek Forward check 271 | if (options.forwardKey(e, player)) { 272 | return cForward; 273 | } 274 | 275 | // Volume Up check 276 | if (options.volumeUpKey(e, player)) { 277 | return cVolumeUp; 278 | } 279 | 280 | // Volume Down check 281 | if (options.volumeDownKey(e, player)) { 282 | return cVolumeDown; 283 | } 284 | 285 | // Mute check 286 | if (options.muteKey(e, player)) { 287 | return cMute; 288 | } 289 | 290 | // Fullscreen check 291 | if (options.fullscreenKey(e, player)) { 292 | return cFullscreen; 293 | } 294 | }; 295 | 296 | function playPauseKey(e) { 297 | // Space bar or MediaPlayPause 298 | return (e.which === 32 || e.which === 179); 299 | } 300 | 301 | function rewindKey(e) { 302 | // Left Arrow or MediaRewind 303 | return (e.which === 37 || e.which === 177); 304 | } 305 | 306 | function forwardKey(e) { 307 | // Right Arrow or MediaForward 308 | return (e.which === 39 || e.which === 176); 309 | } 310 | 311 | function volumeUpKey(e) { 312 | // Up Arrow 313 | return (e.which === 38); 314 | } 315 | 316 | function volumeDownKey(e) { 317 | // Down Arrow 318 | return (e.which === 40); 319 | } 320 | 321 | function muteKey(e) { 322 | // M key 323 | return (e.which === 77); 324 | } 325 | 326 | function fullscreenKey(e) { 327 | // F key 328 | return (e.which === 70); 329 | } 330 | 331 | player.on('keydown', keyDown); 332 | player.on('dblclick', doubleClick); 333 | player.on('mousewheel', mouseScroll); 334 | player.on("DOMMouseScroll", mouseScroll); 335 | 336 | return this; 337 | }; 338 | 339 | videojs.plugin('hotkeys', hotkeys); 340 | }); 341 | --------------------------------------------------------------------------------