├── .gitignore ├── lang ├── de.coffee └── it.coffee ├── demo ├── font │ ├── vjs.eot │ ├── vjs.ttf │ ├── vjs.woff │ └── vjs.svg ├── index.html └── video-js.css ├── screenshots └── chromecast-player.jpg ├── src ├── videojs.chromecast.coffee ├── videojs.chromecast-tech.coffee ├── videojs.chromecast.less └── videojs.chromecast-component.coffee ├── demo-server.js ├── CHANGELOG.md ├── bower.json ├── package.json ├── LICENSE.md ├── Gruntfile.coffee ├── README.md └── dist ├── videojs.chromecast.min.css ├── videojs.chromecast.css ├── videojs.chromecast.min.js └── videojs.chromecast.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /lang/de.coffee: -------------------------------------------------------------------------------- 1 | vjs.addLanguage "de", 2 | "CASTING TO": "WIEDERGABE AUF" 3 | -------------------------------------------------------------------------------- /lang/it.coffee: -------------------------------------------------------------------------------- 1 | vjs.addLanguage "it", 2 | "CASTING TO": "PLAYBACK SU" 3 | -------------------------------------------------------------------------------- /demo/font/vjs.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kim-company/videojs-chromecast/HEAD/demo/font/vjs.eot -------------------------------------------------------------------------------- /demo/font/vjs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kim-company/videojs-chromecast/HEAD/demo/font/vjs.ttf -------------------------------------------------------------------------------- /demo/font/vjs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kim-company/videojs-chromecast/HEAD/demo/font/vjs.woff -------------------------------------------------------------------------------- /screenshots/chromecast-player.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kim-company/videojs-chromecast/HEAD/screenshots/chromecast-player.jpg -------------------------------------------------------------------------------- /src/videojs.chromecast.coffee: -------------------------------------------------------------------------------- 1 | vjs.plugin "chromecast", (options) -> 2 | @chromecastComponent = new vjs.ChromecastComponent(@, options) 3 | @controlBar.addChild @chromecastComponent 4 | -------------------------------------------------------------------------------- /demo-server.js: -------------------------------------------------------------------------------- 1 | var express = require("express"); 2 | var app = express(); 3 | 4 | app.use(express.static(__dirname)); 5 | 6 | app.listen(3000); 7 | console.log("Listening on port 3000"); 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | ## HEAD (Unreleased) 5 | *(no changes)* 6 | 7 | ## 1.1.1 (13.04.2014) 8 | * The Chromecast will no longer stay paused after seeking. (#10) 9 | * If casting is ended while playing, the browser seeks to the last position and plays. (#10) 10 | 11 | ## 1.1.0 (18.02.2014) 12 | * Added `bower.json`. It can now be installed by calling `bower install videojs-chromecast`. (#8) 13 | * Added WebM and HLS support. (#9) 14 | * Fixed compatibility with Video.JS v4.12.0. 15 | 16 | ## 1.0.0 (22.09.2014) 17 | * First release 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-chromecast", 3 | "version": "1.1.1", 4 | "homepage": "https://github.com/kim-company/videojs-chromecast", 5 | "author": { 6 | "name": "KIM Keep In Mind GmbH, srl", 7 | "email": "info@keepinmind.info" 8 | }, 9 | "description": "Videojs plugin for chromecast", 10 | "main": [ 11 | "dist/videojs.chromecast.min.js", 12 | "dist/videojs.chromecast.min.css" 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-chromecast", 3 | "version": "1.1.1", 4 | "homepage": "https://github.com/kim-company/videojs-chromecast", 5 | "author": { 6 | "name": "KIM Keep In Mind GmbH, srl", 7 | "email": "info@keepinmind.info" 8 | }, 9 | "license": "MIT", 10 | "engines": { 11 | "node": ">= 0.8.0" 12 | }, 13 | "dependencies": {}, 14 | "devDependencies": { 15 | "express": "~ 4.12.3", 16 | "grunt": "~ 0.4.0", 17 | "grunt-contrib-uglify": "~ 0.9.1", 18 | "grunt-contrib-clean": "~ 0.6.0", 19 | "grunt-contrib-cssmin": "~ 0.12.2", 20 | "grunt-contrib-coffee": "~ 0.13.0", 21 | "grunt-contrib-less": "~ 1.0.1", 22 | "grunt-banner": "~ 0.3.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 KIM Keep In Mind GmbH, srl 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo - VideoJS Chromecast Plugin 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 24 | 25 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = -> 2 | 3 | # Initialize the configuration 4 | @initConfig 5 | 6 | pkg: @file.readJSON "package.json" 7 | 8 | banner: """ 9 | /*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today('yyyy-mm-dd') %> 10 | * <%= pkg.homepage %> 11 | * Copyright (c) <%= grunt.template.today('yyyy') %> <%= pkg.author.name %>; Licensed <%= pkg.license %> */ 12 | 13 | """ 14 | 15 | clean: 16 | src: "dist/*" 17 | 18 | coffee: 19 | compileJoined: 20 | options: 21 | join: true 22 | files: 23 | "dist/videojs.chromecast.js": [ 24 | "lang/*" 25 | "src/videojs.chromecast.coffee" 26 | "src/videojs.chromecast-component.coffee" 27 | "src/videojs.chromecast-tech.coffee" 28 | ] 29 | 30 | uglify: 31 | options: 32 | compress: 33 | drop_console: true 34 | pure_funcs: ["vjs.log"] 35 | dist: 36 | src: "dist/videojs.chromecast.js" 37 | dest: "dist/videojs.chromecast.min.js" 38 | 39 | less: 40 | development: 41 | files: 42 | "dist/videojs.chromecast.css": "src/videojs.chromecast.less" 43 | 44 | cssmin: 45 | dist: 46 | src: "dist/videojs.chromecast.css" 47 | dest: "dist/videojs.chromecast.min.css" 48 | 49 | usebanner: 50 | options: 51 | position: "top" 52 | banner: "<%= banner %>" 53 | files: 54 | src: [ 55 | "dist/videojs.chromecast.js" 56 | "dist/videojs.chromecast.min.js" 57 | "dist/videojs.chromecast.min.css" 58 | "dist/videojs.chromecast.css" 59 | ] 60 | 61 | # Load external Grunt task plugins 62 | @loadNpmTasks "grunt-contrib-clean" 63 | @loadNpmTasks "grunt-contrib-uglify" 64 | @loadNpmTasks "grunt-contrib-cssmin" 65 | @loadNpmTasks "grunt-contrib-coffee" 66 | @loadNpmTasks "grunt-contrib-less" 67 | @loadNpmTasks "grunt-banner" 68 | 69 | # Default task 70 | @registerTask "default", ["clean", "coffee", "uglify", "less", "cssmin", "usebanner"] 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [VideoJS](http://www.videojs.com) Chromecast Plugin 2 | Displays a Chromecast button in the control bar. The button is only shown if the [Google Cast extension](https://chrome.google.com/webstore/detail/google-cast/boadgeojelhgndaghljhdicfkmllpafd) is installed and a Chromecast is currently available. 3 | 4 | ![video player](https://raw.githubusercontent.com/kim-company/videojs-chromecast/master/screenshots/chromecast-player.jpg) 5 | 6 | ## Getting started 7 | **NOTE:** The Chromecast Plugin won't work if you open the index.html in the browser. It must run on a webserver. 8 | 9 | 1. Add `data-cast-api-enabled="true"` in your `` Tag. 10 | 2. Include `videojs.chromecast.css` and `videojs.chromecast.js` in the ``. 11 | 3. Initialize the VideoJS Player with the Chromecast Plugin like the [configuration example](#configuration-example). 12 | 4. When a Chromecast is available in your network, you should see the cast button in the controlbar. 13 | 14 | If you are not able to configure the player, check out the [demo directory](https://github.com/kim-company/videojs-chromecast/tree/master/demo). 15 | 16 | #### Configuration example 17 | ```javascript 18 | videojs("my_player_id", { 19 | plugins: { 20 | chromecast: { 21 | appId: "AppID of your Chromecast App", 22 | metadata: { 23 | title: "Title", 24 | subtitle: "Subtitle" 25 | } 26 | } 27 | } 28 | }); 29 | ``` 30 | 31 | ## Contributing 32 | [![david-dm](http://img.shields.io/david/dev/kim-company/videojs-chromecast.svg?style=flat)](https://david-dm.org/kim-company/videojs-chromecast/) 33 | 34 | Ensure that you have installed [Node.js](http://www.nodejs.org) and [npm](http://www.npmjs.org/) 35 | 36 | Test that Grunt's CLI is installed by running `grunt --version`. If the command isn't found, run `npm install -g grunt-cli`. For more information about installing Grunt, see the [getting started guide](http://gruntjs.com/getting-started). 37 | 38 | 1. Fork and clone the repository. 39 | 2. Run `npm install` to install the dependencies. 40 | 3. Run `grunt` to grunt this project. 41 | 42 | #### You can test your changes with the included demo 43 | 1. Run `node demo-server.js` to start the server. 44 | 2. See `http://localhost:3000/demo/` in your browser. 45 | -------------------------------------------------------------------------------- /src/videojs.chromecast-tech.coffee: -------------------------------------------------------------------------------- 1 | class vjs.ChromecastTech extends vjs.MediaTechController 2 | @isSupported = -> 3 | @player_.chromecastComponent.apiInitialized 4 | 5 | @canPlaySource = (source) -> 6 | source.type is "video/mp4" or 7 | source.type is "video/webm" or 8 | source.type is "application/x-mpegURL" or 9 | source.type is "application/vnd.apple.mpegURL" 10 | 11 | constructor: (player, options, ready) -> 12 | @featuresVolumeControl = true 13 | @movingMediaElementInDOM = false 14 | @featuresFullscreenResize = false 15 | @featuresProgressEvents = true 16 | 17 | @receiver = options.source.receiver 18 | 19 | super player, options, ready 20 | 21 | @triggerReady() 22 | 23 | createEl: -> 24 | element = document.createElement "div" 25 | element.id = "#{@player_.id_}_chromecast_api" 26 | element.className = "vjs-tech vjs-tech-chromecast" 27 | element.innerHTML = """ 28 |
29 |
30 |
31 |
32 |
#{@localize "CASTING TO"}
#{@receiver}
33 |
34 |
35 | """ 36 | 37 | element.player = @player_ 38 | vjs.insertFirst element, @player_.el() 39 | 40 | element 41 | 42 | ### 43 | MEDIA PLAYER EVENTS 44 | ### 45 | 46 | play: -> 47 | @player_.chromecastComponent.play() 48 | @player_.onPlay() 49 | 50 | pause: -> 51 | @player_.chromecastComponent.pause() 52 | @player_.onPause() 53 | 54 | paused: -> 55 | @player_.chromecastComponent.paused 56 | 57 | currentTime: -> 58 | @player_.chromecastComponent.currentMediaTime 59 | 60 | setCurrentTime: (seconds) -> 61 | @player_.chromecastComponent.seekMedia seconds 62 | 63 | volume: -> 64 | @player_.chromecastComponent.currentVolume 65 | 66 | setVolume: (volume) -> 67 | @player_.chromecastComponent.setMediaVolume volume, false 68 | 69 | muted: -> 70 | @player_.chromecastComponent.muted 71 | 72 | setMuted: (muted) -> 73 | @player_.chromecastComponent.setMediaVolume @player_.chromecastComponent.currentVolume, muted 74 | 75 | supportsFullScreen: -> 76 | false 77 | -------------------------------------------------------------------------------- /dist/videojs.chromecast.min.css: -------------------------------------------------------------------------------- 1 | /*! videojs-chromecast - v1.1.1 - 2015-04-15 2 | * https://github.com/kim-company/videojs-chromecast 3 | * Copyright (c) 2015 KIM Keep In Mind GmbH, srl; Licensed MIT */ 4 | 5 | @font-face{font-family:chromecast;src:url("data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAS8AAsAAAAABHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgCCL8tmNtYXAAAAFoAAAATAAAAEwaVcxXZ2FzcAAAAbQAAAAIAAAACAAAABBnbHlmAAABvAAAAPwAAAD8H/uKE2hlYWQAAAK4AAAANgAAADYCPa1TaGhlYQAAAvAAAAAkAAAAJASAAoRobXR4AAADFAAAABQAAAAUA54AAGxvY2EAAAMoAAAADAAAAAwAKACSbWF4cAAAAzQAAAAgAAAAIAAKAD5uYW1lAAADVAAAAUUAAAFFVxmm7nBvc3QAAAScAAAAIAAAACAAAwAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA5gAB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDmAP/9//8AAAAAACDmAP/9//8AAf/jGgQAAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAQAAP/gAp4B4AAUAB0ALAA7AAATFR4BFx4BFzUhESEeARceARchESEDFTMuAScuASc1FR4BFx4BFzMuAScuASc1FR4BFx4BFzMuAScuAScnBw4GBw0GAg3+rwIFAgIDAQF3/YknSwMOCgoYDhYnDw8VBDUFHRYWOiEpSBwbJAU0BCwjI1s0AeDZAQMCAgUCs/6RBg0HBg4HAdn+S0sOGAoKDgNeNQQVDw8nFiE6FhYdBVw0BSQbHEgpNFsjIywEAAEAAAAAAAD1+MbOXw889QALAgAAAAAA0EC2SwAAAADQQLZLAAD/4AKeAeAAAAAIAAIAAAAAAAAAAQAAAeD/4AAAAp4AAAAAAp4AAQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAABAAAAAp4AAAAAAAAACgAUAB4AfgABAAAABQA8AAQAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIADgBHAAEAAAAAAAMADgAkAAEAAAAAAAQADgBVAAEAAAAAAAUAFgAOAAEAAAAAAAYABwAyAAEAAAAAAAoANABjAAMAAQQJAAEADgAAAAMAAQQJAAIADgBHAAMAAQQJAAMADgAkAAMAAQQJAAQADgBVAAMAAQQJAAUAFgAOAAMAAQQJAAYADgA5AAMAAQQJAAoANABjAGkAYwBvAG0AbwBvAG4AVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG4AUgBlAGcAdQBsAGEAcgBpAGMAbwBtAG8AbwBuAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")format("woff");font-weight:400;font-style:normal}.vjs-chromecast-button{float:right!important;cursor:pointer;width:3em!important}.vjs-chromecast-button:before{content:"\e600";font-family:chromecast!important}.vjs-chromecast-button.connected{color:#66A8CC}.vjs-tech-chromecast .casting-image{position:absolute;top:0;right:0;left:0;bottom:0;background-color:#000;background-repeat:no-repeat;background-size:contain;background-position:center}.vjs-tech-chromecast .casting-overlay{position:absolute;top:0;right:0;left:0;bottom:0;background-color:#000;opacity:.6;cursor:default}.vjs-tech-chromecast .casting-overlay .casting-information{position:absolute;left:15px;bottom:50px;right:15px;height:50px;color:#FFF}.vjs-tech-chromecast .casting-overlay .casting-information .casting-icon{font-family:chromecast!important;font-size:44px;line-height:50px;margin-right:10px;float:left;width:58px;height:50px}.vjs-tech-chromecast .casting-overlay .casting-information .casting-description{height:50px;font-size:20px;line-height:20px}.vjs-tech-chromecast .casting-overlay .casting-information .casting-description small{font-size:11px} -------------------------------------------------------------------------------- /src/videojs.chromecast.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "chromecast"; 3 | src: url("data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAS8AAsAAAAABHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgCCL8tmNtYXAAAAFoAAAATAAAAEwaVcxXZ2FzcAAAAbQAAAAIAAAACAAAABBnbHlmAAABvAAAAPwAAAD8H/uKE2hlYWQAAAK4AAAANgAAADYCPa1TaGhlYQAAAvAAAAAkAAAAJASAAoRobXR4AAADFAAAABQAAAAUA54AAGxvY2EAAAMoAAAADAAAAAwAKACSbWF4cAAAAzQAAAAgAAAAIAAKAD5uYW1lAAADVAAAAUUAAAFFVxmm7nBvc3QAAAScAAAAIAAAACAAAwAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA5gAB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDmAP/9//8AAAAAACDmAP/9//8AAf/jGgQAAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAQAAP/gAp4B4AAUAB0ALAA7AAATFR4BFx4BFzUhESEeARceARchESEDFTMuAScuASc1FR4BFx4BFzMuAScuASc1FR4BFx4BFzMuAScuAScnBw4GBw0GAg3+rwIFAgIDAQF3/YknSwMOCgoYDhYnDw8VBDUFHRYWOiEpSBwbJAU0BCwjI1s0AeDZAQMCAgUCs/6RBg0HBg4HAdn+S0sOGAoKDgNeNQQVDw8nFiE6FhYdBVw0BSQbHEgpNFsjIywEAAEAAAAAAAD1+MbOXw889QALAgAAAAAA0EC2SwAAAADQQLZLAAD/4AKeAeAAAAAIAAIAAAAAAAAAAQAAAeD/4AAAAp4AAAAAAp4AAQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAABAAAAAp4AAAAAAAAACgAUAB4AfgABAAAABQA8AAQAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIADgBHAAEAAAAAAAMADgAkAAEAAAAAAAQADgBVAAEAAAAAAAUAFgAOAAEAAAAAAAYABwAyAAEAAAAAAAoANABjAAMAAQQJAAEADgAAAAMAAQQJAAIADgBHAAMAAQQJAAMADgAkAAMAAQQJAAQADgBVAAMAAQQJAAUAFgAOAAMAAQQJAAYADgA5AAMAAQQJAAoANABjAGkAYwBvAG0AbwBvAG4AVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG4AUgBlAGcAdQBsAGEAcgBpAGMAbwBtAG8AbwBuAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") format("woff"); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | // ChromecastComponent 9 | .vjs-chromecast-button { 10 | float: right !important; 11 | cursor: pointer; 12 | width: 3em !important; 13 | 14 | &:before { 15 | content: "\e600"; 16 | font-family: "chromecast" !important; 17 | } 18 | 19 | &.connected { 20 | color: #66A8CC; 21 | } 22 | } 23 | 24 | // ChromecastTech 25 | .vjs-tech-chromecast { 26 | .casting-image { 27 | position: absolute; 28 | top: 0; right: 0; 29 | left: 0; bottom: 0; 30 | background-color: #000; 31 | background-repeat: no-repeat; 32 | background-size: contain; 33 | background-position: center; 34 | } 35 | 36 | .casting-overlay { 37 | position: absolute; 38 | top: 0; right: 0; 39 | left: 0; bottom: 0; 40 | background-color: #000; 41 | opacity: 0.6; 42 | cursor: default; 43 | 44 | .casting-information { 45 | position: absolute; 46 | left: 15px; bottom: 50px; right: 15px; 47 | height: 50px; 48 | color: #FFF; 49 | 50 | .casting-icon { 51 | font-family: "chromecast" !important; 52 | font-size: 44px; 53 | line-height: 50px; 54 | margin-right: 10px; 55 | float: left; 56 | width: 58px; 57 | height: 50px; 58 | } 59 | 60 | .casting-description { 61 | height: 50px; 62 | font-size: 20px; 63 | line-height: 20px; 64 | 65 | small { font-size: 11px } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /dist/videojs.chromecast.css: -------------------------------------------------------------------------------- 1 | /*! videojs-chromecast - v1.1.1 - 2015-04-15 2 | * https://github.com/kim-company/videojs-chromecast 3 | * Copyright (c) 2015 KIM Keep In Mind GmbH, srl; Licensed MIT */ 4 | 5 | @font-face { 6 | font-family: "chromecast"; 7 | src: url("data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAS8AAsAAAAABHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgCCL8tmNtYXAAAAFoAAAATAAAAEwaVcxXZ2FzcAAAAbQAAAAIAAAACAAAABBnbHlmAAABvAAAAPwAAAD8H/uKE2hlYWQAAAK4AAAANgAAADYCPa1TaGhlYQAAAvAAAAAkAAAAJASAAoRobXR4AAADFAAAABQAAAAUA54AAGxvY2EAAAMoAAAADAAAAAwAKACSbWF4cAAAAzQAAAAgAAAAIAAKAD5uYW1lAAADVAAAAUUAAAFFVxmm7nBvc3QAAAScAAAAIAAAACAAAwAAAAMCAAGQAAUAAAFMAWYAAABHAUwBZgAAAPUAGQCEAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA5gAB4P/g/+AB4AAgAAAAAQAAAAAAAAAAAAAAIAAAAAAAAgAAAAMAAAAUAAMAAQAAABQABAA4AAAACgAIAAIAAgABACDmAP/9//8AAAAAACDmAP/9//8AAf/jGgQAAwABAAAAAAAAAAAAAAABAAH//wAPAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAQAAP/gAp4B4AAUAB0ALAA7AAATFR4BFx4BFzUhESEeARceARchESEDFTMuAScuASc1FR4BFx4BFzMuAScuASc1FR4BFx4BFzMuAScuAScnBw4GBw0GAg3+rwIFAgIDAQF3/YknSwMOCgoYDhYnDw8VBDUFHRYWOiEpSBwbJAU0BCwjI1s0AeDZAQMCAgUCs/6RBg0HBg4HAdn+S0sOGAoKDgNeNQQVDw8nFiE6FhYdBVw0BSQbHEgpNFsjIywEAAEAAAAAAAD1+MbOXw889QALAgAAAAAA0EC2SwAAAADQQLZLAAD/4AKeAeAAAAAIAAIAAAAAAAAAAQAAAeD/4AAAAp4AAAAAAp4AAQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAABAAAAAp4AAAAAAAAACgAUAB4AfgABAAAABQA8AAQAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIADgBHAAEAAAAAAAMADgAkAAEAAAAAAAQADgBVAAEAAAAAAAUAFgAOAAEAAAAAAAYABwAyAAEAAAAAAAoANABjAAMAAQQJAAEADgAAAAMAAQQJAAIADgBHAAMAAQQJAAMADgAkAAMAAQQJAAQADgBVAAMAAQQJAAUAFgAOAAMAAQQJAAYADgA5AAMAAQQJAAoANABjAGkAYwBvAG0AbwBvAG4AVgBlAHIAcwBpAG8AbgAgADEALgAwAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG4AUgBlAGcAdQBsAGEAcgBpAGMAbwBtAG8AbwBuAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") format("woff"); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | .vjs-chromecast-button { 12 | float: right !important; 13 | cursor: pointer; 14 | width: 3em !important; 15 | } 16 | .vjs-chromecast-button:before { 17 | content: "\e600"; 18 | font-family: "chromecast" !important; 19 | } 20 | .vjs-chromecast-button.connected { 21 | color: #66A8CC; 22 | } 23 | .vjs-tech-chromecast .casting-image { 24 | position: absolute; 25 | top: 0; 26 | right: 0; 27 | left: 0; 28 | bottom: 0; 29 | background-color: #000; 30 | background-repeat: no-repeat; 31 | background-size: contain; 32 | background-position: center; 33 | } 34 | .vjs-tech-chromecast .casting-overlay { 35 | position: absolute; 36 | top: 0; 37 | right: 0; 38 | left: 0; 39 | bottom: 0; 40 | background-color: #000; 41 | opacity: 0.6; 42 | cursor: default; 43 | } 44 | .vjs-tech-chromecast .casting-overlay .casting-information { 45 | position: absolute; 46 | left: 15px; 47 | bottom: 50px; 48 | right: 15px; 49 | height: 50px; 50 | color: #FFF; 51 | } 52 | .vjs-tech-chromecast .casting-overlay .casting-information .casting-icon { 53 | font-family: "chromecast" !important; 54 | font-size: 44px; 55 | line-height: 50px; 56 | margin-right: 10px; 57 | float: left; 58 | width: 58px; 59 | height: 50px; 60 | } 61 | .vjs-tech-chromecast .casting-overlay .casting-information .casting-description { 62 | height: 50px; 63 | font-size: 20px; 64 | line-height: 20px; 65 | } 66 | .vjs-tech-chromecast .casting-overlay .casting-information .casting-description small { 67 | font-size: 11px; 68 | } 69 | -------------------------------------------------------------------------------- /src/videojs.chromecast-component.coffee: -------------------------------------------------------------------------------- 1 | class vjs.ChromecastComponent extends vjs.Button 2 | buttonText: "Chromecast" 3 | inactivityTimeout: 2000 4 | 5 | apiInitialized: false 6 | apiSession: null 7 | apiMedia: null 8 | 9 | casting: false 10 | paused: true 11 | muted: false 12 | currentVolume: 1 13 | currentMediaTime: 0 14 | 15 | timer: null 16 | timerStep: 1000 17 | 18 | constructor: (player, @settings) -> 19 | super player, @settings 20 | 21 | @disable() unless player.controls() 22 | @hide() 23 | @initializeApi() 24 | 25 | initializeApi: -> 26 | # Check if the browser is Google Chrome 27 | return unless vjs.IS_CHROME 28 | 29 | # If the Cast APIs arent available yet, retry in 1000ms 30 | if not chrome.cast or not chrome.cast.isAvailable 31 | vjs.log "Cast APIs not available. Retrying..." 32 | setTimeout @initializeApi.bind(@), 1000 33 | return 34 | 35 | vjs.log "Cast APIs are available" 36 | 37 | appId = @settings.appId or chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID 38 | sessionRequest = new chrome.cast.SessionRequest(appId) 39 | 40 | apiConfig = new chrome.cast.ApiConfig(sessionRequest, @sessionJoinedListener, @receiverListener.bind(this)) 41 | 42 | chrome.cast.initialize apiConfig, @onInitSuccess.bind(this), @castError 43 | 44 | sessionJoinedListener: (session) -> 45 | console.log "Session joined" 46 | 47 | receiverListener: (availability) -> 48 | @show() if availability is "available" 49 | 50 | onInitSuccess: -> 51 | @apiInitialized = true 52 | 53 | castError: (castError) -> 54 | vjs.log "Cast Error: #{JSON.stringify(castError)}" 55 | 56 | doLaunch: -> 57 | vjs.log "Cast video: #{@player_.currentSrc()}" 58 | if @apiInitialized 59 | chrome.cast.requestSession @onSessionSuccess.bind(this), @castError 60 | else 61 | vjs.log "Session not initialized" 62 | 63 | onSessionSuccess: (session) -> 64 | vjs.log "Session initialized: #{session.sessionId}" 65 | 66 | @apiSession = session 67 | @addClass "connected" 68 | 69 | mediaInfo = new chrome.cast.media.MediaInfo @player_.currentSrc(), @player_.currentType() 70 | 71 | if @settings.metadata 72 | mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata() 73 | 74 | for key, value of @settings.metadata 75 | mediaInfo.metadata[key] = value 76 | 77 | if @player_.options_.poster 78 | image = new chrome.cast.Image(@player_.options_.poster) 79 | mediaInfo.metadata.images = [image] 80 | 81 | loadRequest = new chrome.cast.media.LoadRequest(mediaInfo) 82 | loadRequest.autoplay = true 83 | loadRequest.currentTime = @player_.currentTime() 84 | 85 | @apiSession.loadMedia loadRequest, @onMediaDiscovered.bind(this), @castError 86 | @apiSession.addUpdateListener @onSessionUpdate.bind(this) 87 | 88 | onMediaDiscovered: (media) -> 89 | @apiMedia = media 90 | @apiMedia.addUpdateListener @onMediaStatusUpdate.bind(this) 91 | 92 | @startProgressTimer @incrementMediaTime.bind(this) 93 | 94 | @player_.loadTech "ChromecastTech", 95 | receiver: @apiSession.receiver.friendlyName 96 | 97 | @casting = true 98 | @paused = @player_.paused() 99 | 100 | # Always show the controlbar 101 | @inactivityTimeout = @player_.options_.inactivityTimeout 102 | @player_.options_.inactivityTimeout = 0 103 | @player_.userActive true 104 | 105 | onSessionUpdate: (isAlive) -> 106 | return unless @apiMedia 107 | 108 | @onStopAppSuccess() if not isAlive 109 | 110 | onMediaStatusUpdate: (isAlive) -> 111 | return unless @apiMedia 112 | 113 | @currentMediaTime = @apiMedia.currentTime 114 | 115 | switch @apiMedia.playerState 116 | when chrome.cast.media.PlayerState.IDLE 117 | @currentMediaTime = 0 118 | @trigger "timeupdate" 119 | @onStopAppSuccess() 120 | when chrome.cast.media.PlayerState.PAUSED 121 | return if @paused 122 | @player_.pause() 123 | @paused = true 124 | when chrome.cast.media.PlayerState.PLAYING 125 | return unless @paused 126 | @player_.play() 127 | @paused = false 128 | 129 | startProgressTimer: (callback) -> 130 | if @timer 131 | clearInterval @timer 132 | @timer = null 133 | 134 | @timer = setInterval(callback.bind(this), @timerStep) 135 | 136 | play: -> 137 | return unless @apiMedia 138 | if @paused 139 | @apiMedia.play null, @mediaCommandSuccessCallback.bind(this, "Playing: " + @apiMedia.sessionId), @onError 140 | @paused = false 141 | 142 | pause: -> 143 | return unless @apiMedia 144 | 145 | unless @paused 146 | @apiMedia.pause null, @mediaCommandSuccessCallback.bind(this, "Paused: " + @apiMedia.sessionId), @onError 147 | @paused = true 148 | 149 | seekMedia: (position) -> 150 | request = new chrome.cast.media.SeekRequest() 151 | request.currentTime = position 152 | # Make sure playback resumes. videoWasPlaying does not survive minification. 153 | request.resumeState = chrome.cast.media.ResumeState.PLAYBACK_START if @player_.controlBar.progressControl.seekBar.videoWasPlaying 154 | 155 | @apiMedia.seek request, @onSeekSuccess.bind(this, position), @onError 156 | 157 | onSeekSuccess: (position) -> 158 | @currentMediaTime = position 159 | 160 | setMediaVolume: (level, mute) -> 161 | return unless @apiMedia 162 | 163 | volume = new chrome.cast.Volume() 164 | volume.level = level 165 | volume.muted = mute 166 | 167 | @currentVolume = volume.level 168 | @muted = mute 169 | 170 | request = new chrome.cast.media.VolumeRequest() 171 | request.volume = volume 172 | 173 | @apiMedia.setVolume request, @mediaCommandSuccessCallback.bind(this, "Volume changed"), @onError 174 | @player_.trigger "volumechange" 175 | 176 | incrementMediaTime: -> 177 | return unless @apiMedia.playerState is chrome.cast.media.PlayerState.PLAYING 178 | 179 | if @currentMediaTime < @apiMedia.media.duration 180 | @currentMediaTime += 1 181 | @trigger "timeupdate" 182 | else 183 | @currentMediaTime = 0 184 | clearInterval @timer 185 | 186 | mediaCommandSuccessCallback: (information, event) -> 187 | vjs.log information 188 | 189 | onError: -> 190 | vjs.log "error" 191 | 192 | # Stops the casting on the Chromecast 193 | stopCasting: -> 194 | @apiSession.stop @onStopAppSuccess.bind(this), @onError 195 | 196 | # Callback when the app has been successfully stopped 197 | onStopAppSuccess: -> 198 | clearInterval @timer 199 | @casting = false 200 | @removeClass "connected" 201 | 202 | @player_.src @player_.options_["sources"] 203 | 204 | # Resume playback if not paused when casting is stopped 205 | unless @paused 206 | @player_.one 'seeked', -> 207 | @player_.play() 208 | @player_.currentTime(@currentMediaTime) 209 | 210 | # Hide the default HTML5 player controls. 211 | @player_.tech.setControls(false) 212 | 213 | # Enable user activity timeout 214 | @player_.options_.inactivityTimeout = @inactivityTimeout 215 | 216 | @apiMedia = null 217 | @apiSession = null 218 | 219 | buildCSSClass: -> 220 | super + "vjs-chromecast-button" 221 | 222 | onClick: -> 223 | super 224 | if @casting then @stopCasting() else @doLaunch() 225 | -------------------------------------------------------------------------------- /dist/videojs.chromecast.min.js: -------------------------------------------------------------------------------- 1 | /*! videojs-chromecast - v1.1.1 - 2015-04-15 2 | * https://github.com/kim-company/videojs-chromecast 3 | * Copyright (c) 2015 KIM Keep In Mind GmbH, srl; Licensed MIT */ 4 | 5 | (function(){var a=function(a,c){function d(){this.constructor=a}for(var e in c)b.call(c,e)&&(a[e]=c[e]);return d.prototype=c.prototype,a.prototype=new d,a.__super__=c.prototype,a},b={}.hasOwnProperty;vjs.addLanguage("de",{"CASTING TO":"WIEDERGABE AUF"}),vjs.addLanguage("it",{"CASTING TO":"PLAYBACK SU"}),vjs.plugin("chromecast",function(a){return this.chromecastComponent=new vjs.ChromecastComponent(this,a),this.controlBar.addChild(this.chromecastComponent)}),vjs.ChromecastComponent=function(b){function c(a,b){this.settings=b,c.__super__.constructor.call(this,a,this.settings),a.controls()||this.disable(),this.hide(),this.initializeApi()}return a(c,b),c.prototype.buttonText="Chromecast",c.prototype.inactivityTimeout=2e3,c.prototype.apiInitialized=!1,c.prototype.apiSession=null,c.prototype.apiMedia=null,c.prototype.casting=!1,c.prototype.paused=!0,c.prototype.muted=!1,c.prototype.currentVolume=1,c.prototype.currentMediaTime=0,c.prototype.timer=null,c.prototype.timerStep=1e3,c.prototype.initializeApi=function(){var a,b,c;if(vjs.IS_CHROME)return chrome.cast&&chrome.cast.isAvailable?(b=this.settings.appId||chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID,c=new chrome.cast.SessionRequest(b),a=new chrome.cast.ApiConfig(c,this.sessionJoinedListener,this.receiverListener.bind(this)),chrome.cast.initialize(a,this.onInitSuccess.bind(this),this.castError)):void setTimeout(this.initializeApi.bind(this),1e3)},c.prototype.sessionJoinedListener=function(a){return void 0},c.prototype.receiverListener=function(a){return"available"===a?this.show():void 0},c.prototype.onInitSuccess=function(){return this.apiInitialized=!0},c.prototype.castError=function(a){return vjs.log("Cast Error: "+JSON.stringify(a))},c.prototype.doLaunch=function(){return this.apiInitialized?chrome.cast.requestSession(this.onSessionSuccess.bind(this),this.castError):vjs.log("Session not initialized")},c.prototype.onSessionSuccess=function(a){var b,c,d,e,f,g;if(this.apiSession=a,this.addClass("connected"),e=new chrome.cast.media.MediaInfo(this.player_.currentSrc(),this.player_.currentType()),this.settings.metadata){e.metadata=new chrome.cast.media.GenericMediaMetadata,f=this.settings.metadata;for(c in f)g=f[c],e.metadata[c]=g;this.player_.options_.poster&&(b=new chrome.cast.Image(this.player_.options_.poster),e.metadata.images=[b])}return d=new chrome.cast.media.LoadRequest(e),d.autoplay=!0,d.currentTime=this.player_.currentTime(),this.apiSession.loadMedia(d,this.onMediaDiscovered.bind(this),this.castError),this.apiSession.addUpdateListener(this.onSessionUpdate.bind(this))},c.prototype.onMediaDiscovered=function(a){return this.apiMedia=a,this.apiMedia.addUpdateListener(this.onMediaStatusUpdate.bind(this)),this.startProgressTimer(this.incrementMediaTime.bind(this)),this.player_.loadTech("ChromecastTech",{receiver:this.apiSession.receiver.friendlyName}),this.casting=!0,this.paused=this.player_.paused(),this.inactivityTimeout=this.player_.options_.inactivityTimeout,this.player_.options_.inactivityTimeout=0,this.player_.userActive(!0)},c.prototype.onSessionUpdate=function(a){return this.apiMedia?a?void 0:this.onStopAppSuccess():void 0},c.prototype.onMediaStatusUpdate=function(a){if(this.apiMedia)switch(this.currentMediaTime=this.apiMedia.currentTime,this.apiMedia.playerState){case chrome.cast.media.PlayerState.IDLE:return this.currentMediaTime=0,this.trigger("timeupdate"),this.onStopAppSuccess();case chrome.cast.media.PlayerState.PAUSED:if(this.paused)return;return this.player_.pause(),this.paused=!0;case chrome.cast.media.PlayerState.PLAYING:if(!this.paused)return;return this.player_.play(),this.paused=!1}},c.prototype.startProgressTimer=function(a){return this.timer&&(clearInterval(this.timer),this.timer=null),this.timer=setInterval(a.bind(this),this.timerStep)},c.prototype.play=function(){return this.apiMedia&&this.paused?(this.apiMedia.play(null,this.mediaCommandSuccessCallback.bind(this,"Playing: "+this.apiMedia.sessionId),this.onError),this.paused=!1):void 0},c.prototype.pause=function(){return this.apiMedia?this.paused?void 0:(this.apiMedia.pause(null,this.mediaCommandSuccessCallback.bind(this,"Paused: "+this.apiMedia.sessionId),this.onError),this.paused=!0):void 0},c.prototype.seekMedia=function(a){var b;return b=new chrome.cast.media.SeekRequest,b.currentTime=a,this.player_.controlBar.progressControl.seekBar.videoWasPlaying&&(b.resumeState=chrome.cast.media.ResumeState.PLAYBACK_START),this.apiMedia.seek(b,this.onSeekSuccess.bind(this,a),this.onError)},c.prototype.onSeekSuccess=function(a){return this.currentMediaTime=a},c.prototype.setMediaVolume=function(a,b){var c,d;if(this.apiMedia)return d=new chrome.cast.Volume,d.level=a,d.muted=b,this.currentVolume=d.level,this.muted=b,c=new chrome.cast.media.VolumeRequest,c.volume=d,this.apiMedia.setVolume(c,this.mediaCommandSuccessCallback.bind(this,"Volume changed"),this.onError),this.player_.trigger("volumechange")},c.prototype.incrementMediaTime=function(){return this.apiMedia.playerState===chrome.cast.media.PlayerState.PLAYING?this.currentMediaTime\n
\n
\n
\n
'+this.localize("CASTING TO")+"
"+this.receiver+"
\n
\n
",a.player=this.player_,vjs.insertFirst(a,this.player_.el()),a},c.prototype.play=function(){return this.player_.chromecastComponent.play(),this.player_.onPlay()},c.prototype.pause=function(){return this.player_.chromecastComponent.pause(),this.player_.onPause()},c.prototype.paused=function(){return this.player_.chromecastComponent.paused},c.prototype.currentTime=function(){return this.player_.chromecastComponent.currentMediaTime},c.prototype.setCurrentTime=function(a){return this.player_.chromecastComponent.seekMedia(a)},c.prototype.volume=function(){return this.player_.chromecastComponent.currentVolume},c.prototype.setVolume=function(a){return this.player_.chromecastComponent.setMediaVolume(a,!1)},c.prototype.muted=function(){return this.player_.chromecastComponent.muted},c.prototype.setMuted=function(a){return this.player_.chromecastComponent.setMediaVolume(this.player_.chromecastComponent.currentVolume,a)},c.prototype.supportsFullScreen=function(){return!1},c}(vjs.MediaTechController)}).call(this); -------------------------------------------------------------------------------- /demo/font/vjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /dist/videojs.chromecast.js: -------------------------------------------------------------------------------- 1 | /*! videojs-chromecast - v1.1.1 - 2015-04-15 2 | * https://github.com/kim-company/videojs-chromecast 3 | * Copyright (c) 2015 KIM Keep In Mind GmbH, srl; Licensed MIT */ 4 | 5 | (function() { 6 | var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, 7 | hasProp = {}.hasOwnProperty; 8 | 9 | vjs.addLanguage("de", { 10 | "CASTING TO": "WIEDERGABE AUF" 11 | }); 12 | 13 | vjs.addLanguage("it", { 14 | "CASTING TO": "PLAYBACK SU" 15 | }); 16 | 17 | vjs.plugin("chromecast", function(options) { 18 | this.chromecastComponent = new vjs.ChromecastComponent(this, options); 19 | return this.controlBar.addChild(this.chromecastComponent); 20 | }); 21 | 22 | vjs.ChromecastComponent = (function(superClass) { 23 | extend(ChromecastComponent, superClass); 24 | 25 | ChromecastComponent.prototype.buttonText = "Chromecast"; 26 | 27 | ChromecastComponent.prototype.inactivityTimeout = 2000; 28 | 29 | ChromecastComponent.prototype.apiInitialized = false; 30 | 31 | ChromecastComponent.prototype.apiSession = null; 32 | 33 | ChromecastComponent.prototype.apiMedia = null; 34 | 35 | ChromecastComponent.prototype.casting = false; 36 | 37 | ChromecastComponent.prototype.paused = true; 38 | 39 | ChromecastComponent.prototype.muted = false; 40 | 41 | ChromecastComponent.prototype.currentVolume = 1; 42 | 43 | ChromecastComponent.prototype.currentMediaTime = 0; 44 | 45 | ChromecastComponent.prototype.timer = null; 46 | 47 | ChromecastComponent.prototype.timerStep = 1000; 48 | 49 | function ChromecastComponent(player, settings) { 50 | this.settings = settings; 51 | ChromecastComponent.__super__.constructor.call(this, player, this.settings); 52 | if (!player.controls()) { 53 | this.disable(); 54 | } 55 | this.hide(); 56 | this.initializeApi(); 57 | } 58 | 59 | ChromecastComponent.prototype.initializeApi = function() { 60 | var apiConfig, appId, sessionRequest; 61 | if (!vjs.IS_CHROME) { 62 | return; 63 | } 64 | if (!chrome.cast || !chrome.cast.isAvailable) { 65 | vjs.log("Cast APIs not available. Retrying..."); 66 | setTimeout(this.initializeApi.bind(this), 1000); 67 | return; 68 | } 69 | vjs.log("Cast APIs are available"); 70 | appId = this.settings.appId || chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID; 71 | sessionRequest = new chrome.cast.SessionRequest(appId); 72 | apiConfig = new chrome.cast.ApiConfig(sessionRequest, this.sessionJoinedListener, this.receiverListener.bind(this)); 73 | return chrome.cast.initialize(apiConfig, this.onInitSuccess.bind(this), this.castError); 74 | }; 75 | 76 | ChromecastComponent.prototype.sessionJoinedListener = function(session) { 77 | return console.log("Session joined"); 78 | }; 79 | 80 | ChromecastComponent.prototype.receiverListener = function(availability) { 81 | if (availability === "available") { 82 | return this.show(); 83 | } 84 | }; 85 | 86 | ChromecastComponent.prototype.onInitSuccess = function() { 87 | return this.apiInitialized = true; 88 | }; 89 | 90 | ChromecastComponent.prototype.castError = function(castError) { 91 | return vjs.log("Cast Error: " + (JSON.stringify(castError))); 92 | }; 93 | 94 | ChromecastComponent.prototype.doLaunch = function() { 95 | vjs.log("Cast video: " + (this.player_.currentSrc())); 96 | if (this.apiInitialized) { 97 | return chrome.cast.requestSession(this.onSessionSuccess.bind(this), this.castError); 98 | } else { 99 | return vjs.log("Session not initialized"); 100 | } 101 | }; 102 | 103 | ChromecastComponent.prototype.onSessionSuccess = function(session) { 104 | var image, key, loadRequest, mediaInfo, ref, value; 105 | vjs.log("Session initialized: " + session.sessionId); 106 | this.apiSession = session; 107 | this.addClass("connected"); 108 | mediaInfo = new chrome.cast.media.MediaInfo(this.player_.currentSrc(), this.player_.currentType()); 109 | if (this.settings.metadata) { 110 | mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); 111 | ref = this.settings.metadata; 112 | for (key in ref) { 113 | value = ref[key]; 114 | mediaInfo.metadata[key] = value; 115 | } 116 | if (this.player_.options_.poster) { 117 | image = new chrome.cast.Image(this.player_.options_.poster); 118 | mediaInfo.metadata.images = [image]; 119 | } 120 | } 121 | loadRequest = new chrome.cast.media.LoadRequest(mediaInfo); 122 | loadRequest.autoplay = true; 123 | loadRequest.currentTime = this.player_.currentTime(); 124 | this.apiSession.loadMedia(loadRequest, this.onMediaDiscovered.bind(this), this.castError); 125 | return this.apiSession.addUpdateListener(this.onSessionUpdate.bind(this)); 126 | }; 127 | 128 | ChromecastComponent.prototype.onMediaDiscovered = function(media) { 129 | this.apiMedia = media; 130 | this.apiMedia.addUpdateListener(this.onMediaStatusUpdate.bind(this)); 131 | this.startProgressTimer(this.incrementMediaTime.bind(this)); 132 | this.player_.loadTech("ChromecastTech", { 133 | receiver: this.apiSession.receiver.friendlyName 134 | }); 135 | this.casting = true; 136 | this.paused = this.player_.paused(); 137 | this.inactivityTimeout = this.player_.options_.inactivityTimeout; 138 | this.player_.options_.inactivityTimeout = 0; 139 | return this.player_.userActive(true); 140 | }; 141 | 142 | ChromecastComponent.prototype.onSessionUpdate = function(isAlive) { 143 | if (!this.apiMedia) { 144 | return; 145 | } 146 | if (!isAlive) { 147 | return this.onStopAppSuccess(); 148 | } 149 | }; 150 | 151 | ChromecastComponent.prototype.onMediaStatusUpdate = function(isAlive) { 152 | if (!this.apiMedia) { 153 | return; 154 | } 155 | this.currentMediaTime = this.apiMedia.currentTime; 156 | switch (this.apiMedia.playerState) { 157 | case chrome.cast.media.PlayerState.IDLE: 158 | this.currentMediaTime = 0; 159 | this.trigger("timeupdate"); 160 | return this.onStopAppSuccess(); 161 | case chrome.cast.media.PlayerState.PAUSED: 162 | if (this.paused) { 163 | return; 164 | } 165 | this.player_.pause(); 166 | return this.paused = true; 167 | case chrome.cast.media.PlayerState.PLAYING: 168 | if (!this.paused) { 169 | return; 170 | } 171 | this.player_.play(); 172 | return this.paused = false; 173 | } 174 | }; 175 | 176 | ChromecastComponent.prototype.startProgressTimer = function(callback) { 177 | if (this.timer) { 178 | clearInterval(this.timer); 179 | this.timer = null; 180 | } 181 | return this.timer = setInterval(callback.bind(this), this.timerStep); 182 | }; 183 | 184 | ChromecastComponent.prototype.play = function() { 185 | if (!this.apiMedia) { 186 | return; 187 | } 188 | if (this.paused) { 189 | this.apiMedia.play(null, this.mediaCommandSuccessCallback.bind(this, "Playing: " + this.apiMedia.sessionId), this.onError); 190 | return this.paused = false; 191 | } 192 | }; 193 | 194 | ChromecastComponent.prototype.pause = function() { 195 | if (!this.apiMedia) { 196 | return; 197 | } 198 | if (!this.paused) { 199 | this.apiMedia.pause(null, this.mediaCommandSuccessCallback.bind(this, "Paused: " + this.apiMedia.sessionId), this.onError); 200 | return this.paused = true; 201 | } 202 | }; 203 | 204 | ChromecastComponent.prototype.seekMedia = function(position) { 205 | var request; 206 | request = new chrome.cast.media.SeekRequest(); 207 | request.currentTime = position; 208 | if (this.player_.controlBar.progressControl.seekBar.videoWasPlaying) { 209 | request.resumeState = chrome.cast.media.ResumeState.PLAYBACK_START; 210 | } 211 | return this.apiMedia.seek(request, this.onSeekSuccess.bind(this, position), this.onError); 212 | }; 213 | 214 | ChromecastComponent.prototype.onSeekSuccess = function(position) { 215 | return this.currentMediaTime = position; 216 | }; 217 | 218 | ChromecastComponent.prototype.setMediaVolume = function(level, mute) { 219 | var request, volume; 220 | if (!this.apiMedia) { 221 | return; 222 | } 223 | volume = new chrome.cast.Volume(); 224 | volume.level = level; 225 | volume.muted = mute; 226 | this.currentVolume = volume.level; 227 | this.muted = mute; 228 | request = new chrome.cast.media.VolumeRequest(); 229 | request.volume = volume; 230 | this.apiMedia.setVolume(request, this.mediaCommandSuccessCallback.bind(this, "Volume changed"), this.onError); 231 | return this.player_.trigger("volumechange"); 232 | }; 233 | 234 | ChromecastComponent.prototype.incrementMediaTime = function() { 235 | if (this.apiMedia.playerState !== chrome.cast.media.PlayerState.PLAYING) { 236 | return; 237 | } 238 | if (this.currentMediaTime < this.apiMedia.media.duration) { 239 | this.currentMediaTime += 1; 240 | return this.trigger("timeupdate"); 241 | } else { 242 | this.currentMediaTime = 0; 243 | return clearInterval(this.timer); 244 | } 245 | }; 246 | 247 | ChromecastComponent.prototype.mediaCommandSuccessCallback = function(information, event) { 248 | return vjs.log(information); 249 | }; 250 | 251 | ChromecastComponent.prototype.onError = function() { 252 | return vjs.log("error"); 253 | }; 254 | 255 | ChromecastComponent.prototype.stopCasting = function() { 256 | return this.apiSession.stop(this.onStopAppSuccess.bind(this), this.onError); 257 | }; 258 | 259 | ChromecastComponent.prototype.onStopAppSuccess = function() { 260 | clearInterval(this.timer); 261 | this.casting = false; 262 | this.removeClass("connected"); 263 | this.player_.src(this.player_.options_["sources"]); 264 | if (!this.paused) { 265 | this.player_.one('seeked', function() { 266 | return this.player_.play(); 267 | }); 268 | } 269 | this.player_.currentTime(this.currentMediaTime); 270 | this.player_.tech.setControls(false); 271 | this.player_.options_.inactivityTimeout = this.inactivityTimeout; 272 | this.apiMedia = null; 273 | return this.apiSession = null; 274 | }; 275 | 276 | ChromecastComponent.prototype.buildCSSClass = function() { 277 | return ChromecastComponent.__super__.buildCSSClass.apply(this, arguments) + "vjs-chromecast-button"; 278 | }; 279 | 280 | ChromecastComponent.prototype.onClick = function() { 281 | ChromecastComponent.__super__.onClick.apply(this, arguments); 282 | if (this.casting) { 283 | return this.stopCasting(); 284 | } else { 285 | return this.doLaunch(); 286 | } 287 | }; 288 | 289 | return ChromecastComponent; 290 | 291 | })(vjs.Button); 292 | 293 | vjs.ChromecastTech = (function(superClass) { 294 | extend(ChromecastTech, superClass); 295 | 296 | ChromecastTech.isSupported = function() { 297 | return this.player_.chromecastComponent.apiInitialized; 298 | }; 299 | 300 | ChromecastTech.canPlaySource = function(source) { 301 | return source.type === "video/mp4" || source.type === "video/webm" || source.type === "application/x-mpegURL" || source.type === "application/vnd.apple.mpegURL"; 302 | }; 303 | 304 | function ChromecastTech(player, options, ready) { 305 | this.featuresVolumeControl = true; 306 | this.movingMediaElementInDOM = false; 307 | this.featuresFullscreenResize = false; 308 | this.featuresProgressEvents = true; 309 | this.receiver = options.source.receiver; 310 | ChromecastTech.__super__.constructor.call(this, player, options, ready); 311 | this.triggerReady(); 312 | } 313 | 314 | ChromecastTech.prototype.createEl = function() { 315 | var element; 316 | element = document.createElement("div"); 317 | element.id = this.player_.id_ + "_chromecast_api"; 318 | element.className = "vjs-tech vjs-tech-chromecast"; 319 | element.innerHTML = "
\n
\n
\n
\n
" + (this.localize("CASTING TO")) + "
" + this.receiver + "
\n
\n
"; 320 | element.player = this.player_; 321 | vjs.insertFirst(element, this.player_.el()); 322 | return element; 323 | }; 324 | 325 | 326 | /* 327 | MEDIA PLAYER EVENTS 328 | */ 329 | 330 | ChromecastTech.prototype.play = function() { 331 | this.player_.chromecastComponent.play(); 332 | return this.player_.onPlay(); 333 | }; 334 | 335 | ChromecastTech.prototype.pause = function() { 336 | this.player_.chromecastComponent.pause(); 337 | return this.player_.onPause(); 338 | }; 339 | 340 | ChromecastTech.prototype.paused = function() { 341 | return this.player_.chromecastComponent.paused; 342 | }; 343 | 344 | ChromecastTech.prototype.currentTime = function() { 345 | return this.player_.chromecastComponent.currentMediaTime; 346 | }; 347 | 348 | ChromecastTech.prototype.setCurrentTime = function(seconds) { 349 | return this.player_.chromecastComponent.seekMedia(seconds); 350 | }; 351 | 352 | ChromecastTech.prototype.volume = function() { 353 | return this.player_.chromecastComponent.currentVolume; 354 | }; 355 | 356 | ChromecastTech.prototype.setVolume = function(volume) { 357 | return this.player_.chromecastComponent.setMediaVolume(volume, false); 358 | }; 359 | 360 | ChromecastTech.prototype.muted = function() { 361 | return this.player_.chromecastComponent.muted; 362 | }; 363 | 364 | ChromecastTech.prototype.setMuted = function(muted) { 365 | return this.player_.chromecastComponent.setMediaVolume(this.player_.chromecastComponent.currentVolume, muted); 366 | }; 367 | 368 | ChromecastTech.prototype.supportsFullScreen = function() { 369 | return false; 370 | }; 371 | 372 | return ChromecastTech; 373 | 374 | })(vjs.MediaTechController); 375 | 376 | }).call(this); 377 | -------------------------------------------------------------------------------- /demo/video-js.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Video.js Default Styles (http://videojs.com) 3 | Version 4.12.5 4 | Create your own skin at http://designer.videojs.com 5 | */ 6 | /* SKIN 7 | ================================================================================ 8 | The main class name for all skin-specific styles. To make your own skin, 9 | replace all occurrences of 'vjs-default-skin' with a new name. Then add your new 10 | skin name to your video tag instead of the default skin. 11 | e.g.