├── .gitignore
├── CHANGELOG.md
├── Gruntfile.coffee
├── LICENSE.md
├── README.md
├── bower.json
├── demo-server.js
├── demo
├── font
│ ├── vjs.eot
│ ├── vjs.svg
│ ├── vjs.ttf
│ └── vjs.woff
├── index.html
├── video-js.css
└── video.dev.js
├── dist
├── videojs.chromecast.css
├── videojs.chromecast.js
├── videojs.chromecast.min.css
└── videojs.chromecast.min.js
├── lang
├── de.coffee
└── it.coffee
├── package.json
├── screenshots
└── chromecast-player.jpg
└── src
├── videojs.chromecast-component.coffee
├── videojs.chromecast-tech.coffee
├── videojs.chromecast.coffee
└── videojs.chromecast.less
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | 
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 | [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/demo/font/vjs.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kim-company/videojs-chromecast/72b141247ad83230e602c199d76475bdeb22f179/demo/font/vjs.eot
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/demo/font/vjs.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kim-company/videojs-chromecast/72b141247ad83230e602c199d76475bdeb22f179/demo/font/vjs.ttf
--------------------------------------------------------------------------------
/demo/font/vjs.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kim-company/videojs-chromecast/72b141247ad83230e602c199d76475bdeb22f179/demo/font/vjs.woff
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Demo - VideoJS Chromecast Plugin
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 | To view this video please enable JavaScript, and consider upgrading to a web browser that supports HTML5 video
23 |
24 |
25 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/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.
12 | */
13 | .vjs-default-skin {
14 | color: #cccccc;
15 | }
16 | /* Custom Icon Font
17 | --------------------------------------------------------------------------------
18 | The control icons are from a custom font. Each icon corresponds to a character
19 | (e.g. "\e001"). Font icons allow for easy scaling and coloring of icons.
20 | */
21 | @font-face {
22 | font-family: 'VideoJS';
23 | src: url('font/vjs.eot');
24 | src: url('font/vjs.eot?#iefix') format('embedded-opentype'), url('font/vjs.woff') format('woff'), url('font/vjs.ttf') format('truetype'), url('font/vjs.svg#icomoon') format('svg');
25 | font-weight: normal;
26 | font-style: normal;
27 | }
28 | /* Base UI Component Classes
29 | --------------------------------------------------------------------------------
30 | */
31 | /* Slider - used for Volume bar and Seek bar */
32 | .vjs-default-skin .vjs-slider {
33 | /* Replace browser focus highlight with handle highlight */
34 | outline: 0;
35 | position: relative;
36 | cursor: pointer;
37 | padding: 0;
38 | /* background-color-with-alpha */
39 | background-color: #333333;
40 | background-color: rgba(51, 51, 51, 0.9);
41 | }
42 | .vjs-default-skin .vjs-slider:focus {
43 | /* box-shadow */
44 | -webkit-box-shadow: 0 0 2em #ffffff;
45 | -moz-box-shadow: 0 0 2em #ffffff;
46 | box-shadow: 0 0 2em #ffffff;
47 | }
48 | .vjs-default-skin .vjs-slider-handle {
49 | position: absolute;
50 | /* Needed for IE6 */
51 | left: 0;
52 | top: 0;
53 | }
54 | .vjs-default-skin .vjs-slider-handle:before {
55 | content: "\e009";
56 | font-family: VideoJS;
57 | font-size: 1em;
58 | line-height: 1;
59 | text-align: center;
60 | text-shadow: 0em 0em 1em #fff;
61 | position: absolute;
62 | top: 0;
63 | left: 0;
64 | /* Rotate the square icon to make a diamond */
65 | /* transform */
66 | -webkit-transform: rotate(-45deg);
67 | -moz-transform: rotate(-45deg);
68 | -ms-transform: rotate(-45deg);
69 | -o-transform: rotate(-45deg);
70 | transform: rotate(-45deg);
71 | }
72 | /* Control Bar
73 | --------------------------------------------------------------------------------
74 | The default control bar that is a container for most of the controls.
75 | */
76 | .vjs-default-skin .vjs-control-bar {
77 | /* Start hidden */
78 | display: none;
79 | position: absolute;
80 | /* Place control bar at the bottom of the player box/video.
81 | If you want more margin below the control bar, add more height. */
82 | bottom: 0;
83 | /* Use left/right to stretch to 100% width of player div */
84 | left: 0;
85 | right: 0;
86 | /* Height includes any margin you want above or below control items */
87 | height: 3.0em;
88 | /* background-color-with-alpha */
89 | background-color: #07141e;
90 | background-color: rgba(7, 20, 30, 0.7);
91 | }
92 | /* Show the control bar only once the video has started playing */
93 | .vjs-default-skin.vjs-has-started .vjs-control-bar {
94 | display: block;
95 | /* Visibility needed to make sure things hide in older browsers too. */
96 |
97 | visibility: visible;
98 | opacity: 1;
99 | /* transition */
100 | -webkit-transition: visibility 0.1s, opacity 0.1s;
101 | -moz-transition: visibility 0.1s, opacity 0.1s;
102 | -o-transition: visibility 0.1s, opacity 0.1s;
103 | transition: visibility 0.1s, opacity 0.1s;
104 | }
105 | /* Hide the control bar when the video is playing and the user is inactive */
106 | .vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
107 | display: block;
108 | visibility: hidden;
109 | opacity: 0;
110 | /* transition */
111 | -webkit-transition: visibility 1s, opacity 1s;
112 | -moz-transition: visibility 1s, opacity 1s;
113 | -o-transition: visibility 1s, opacity 1s;
114 | transition: visibility 1s, opacity 1s;
115 | }
116 | .vjs-default-skin.vjs-controls-disabled .vjs-control-bar {
117 | display: none;
118 | }
119 | .vjs-default-skin.vjs-using-native-controls .vjs-control-bar {
120 | display: none;
121 | }
122 | /* The control bar shouldn't show after an error */
123 | .vjs-default-skin.vjs-error .vjs-control-bar {
124 | display: none;
125 | }
126 | /* Don't hide the control bar if it's audio */
127 | .vjs-audio.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {
128 | opacity: 1;
129 | visibility: visible;
130 | }
131 | /* IE8 is flakey with fonts, and you have to change the actual content to force
132 | fonts to show/hide properly.
133 | - "\9" IE8 hack didn't work for this
134 | - Found in XP IE8 from http://modern.ie. Does not show up in "IE8 mode" in IE9
135 | */
136 | @media \0screen {
137 | .vjs-default-skin.vjs-user-inactive.vjs-playing .vjs-control-bar :before {
138 | content: "";
139 | }
140 | }
141 | /* General styles for individual controls. */
142 | .vjs-default-skin .vjs-control {
143 | outline: none;
144 | position: relative;
145 | float: left;
146 | text-align: center;
147 | margin: 0;
148 | padding: 0;
149 | height: 3.0em;
150 | width: 4em;
151 | }
152 | /* Font button icons */
153 | .vjs-default-skin .vjs-control:before {
154 | font-family: VideoJS;
155 | font-size: 1.5em;
156 | line-height: 2;
157 | position: absolute;
158 | top: 0;
159 | left: 0;
160 | width: 100%;
161 | height: 100%;
162 | text-align: center;
163 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
164 | }
165 | /* Replacement for focus outline */
166 | .vjs-default-skin .vjs-control:focus:before,
167 | .vjs-default-skin .vjs-control:hover:before {
168 | text-shadow: 0em 0em 1em #ffffff;
169 | }
170 | .vjs-default-skin .vjs-control:focus {
171 | /* outline: 0; */
172 | /* keyboard-only users cannot see the focus on several of the UI elements when
173 | this is set to 0 */
174 |
175 | }
176 | /* Hide control text visually, but have it available for screenreaders */
177 | .vjs-default-skin .vjs-control-text {
178 | /* hide-visually */
179 | border: 0;
180 | clip: rect(0 0 0 0);
181 | height: 1px;
182 | margin: -1px;
183 | overflow: hidden;
184 | padding: 0;
185 | position: absolute;
186 | width: 1px;
187 | }
188 | /* Play/Pause
189 | --------------------------------------------------------------------------------
190 | */
191 | .vjs-default-skin .vjs-play-control {
192 | width: 5em;
193 | cursor: pointer;
194 | }
195 | .vjs-default-skin .vjs-play-control:before {
196 | content: "\e001";
197 | }
198 | .vjs-default-skin.vjs-playing .vjs-play-control:before {
199 | content: "\e002";
200 | }
201 | /* Playback toggle
202 | --------------------------------------------------------------------------------
203 | */
204 | .vjs-default-skin .vjs-playback-rate .vjs-playback-rate-value {
205 | font-size: 1.5em;
206 | line-height: 2;
207 | position: absolute;
208 | top: 0;
209 | left: 0;
210 | width: 100%;
211 | height: 100%;
212 | text-align: center;
213 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
214 | }
215 | .vjs-default-skin .vjs-playback-rate.vjs-menu-button .vjs-menu .vjs-menu-content {
216 | width: 4em;
217 | left: -2em;
218 | list-style: none;
219 | }
220 | /* Volume/Mute
221 | -------------------------------------------------------------------------------- */
222 | .vjs-default-skin .vjs-mute-control,
223 | .vjs-default-skin .vjs-volume-menu-button {
224 | cursor: pointer;
225 | float: right;
226 | }
227 | .vjs-default-skin .vjs-mute-control:before,
228 | .vjs-default-skin .vjs-volume-menu-button:before {
229 | content: "\e006";
230 | }
231 | .vjs-default-skin .vjs-mute-control.vjs-vol-0:before,
232 | .vjs-default-skin .vjs-volume-menu-button.vjs-vol-0:before {
233 | content: "\e003";
234 | }
235 | .vjs-default-skin .vjs-mute-control.vjs-vol-1:before,
236 | .vjs-default-skin .vjs-volume-menu-button.vjs-vol-1:before {
237 | content: "\e004";
238 | }
239 | .vjs-default-skin .vjs-mute-control.vjs-vol-2:before,
240 | .vjs-default-skin .vjs-volume-menu-button.vjs-vol-2:before {
241 | content: "\e005";
242 | }
243 | .vjs-default-skin .vjs-volume-control {
244 | width: 5em;
245 | float: right;
246 | }
247 | .vjs-default-skin .vjs-volume-bar {
248 | width: 5em;
249 | height: 0.6em;
250 | margin: 1.1em auto 0;
251 | }
252 | .vjs-default-skin .vjs-volume-level {
253 | position: absolute;
254 | top: 0;
255 | left: 0;
256 | height: 0.5em;
257 | /* assuming volume starts at 1.0 */
258 |
259 | width: 100%;
260 | background: #66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat;
261 | }
262 | .vjs-default-skin .vjs-volume-bar .vjs-volume-handle {
263 | width: 0.5em;
264 | height: 0.5em;
265 | /* Assumes volume starts at 1.0. If you change the size of the
266 | handle relative to the volume bar, you'll need to update this value
267 | too. */
268 |
269 | left: 4.5em;
270 | }
271 | .vjs-default-skin .vjs-volume-handle:before {
272 | font-size: 0.9em;
273 | top: -0.2em;
274 | left: -0.2em;
275 | width: 1em;
276 | height: 1em;
277 | }
278 | /* The volume menu button is like menu buttons (captions/subtitles) but works
279 | a little differently. It needs to be possible to tab to the volume slider
280 | without hitting space bar on the menu button. To do this we're not using
281 | display:none to hide the slider menu by default, and instead setting the
282 | width and height to zero. */
283 | .vjs-default-skin .vjs-volume-menu-button .vjs-menu {
284 | display: block;
285 | width: 0;
286 | height: 0;
287 | border-top-color: transparent;
288 | }
289 | .vjs-default-skin .vjs-volume-menu-button .vjs-menu .vjs-menu-content {
290 | height: 0;
291 | width: 0;
292 | }
293 | .vjs-default-skin .vjs-volume-menu-button:hover .vjs-menu,
294 | .vjs-default-skin .vjs-volume-menu-button .vjs-menu.vjs-lock-showing {
295 | border-top-color: rgba(7, 40, 50, 0.5);
296 | /* Same as ul background */
297 |
298 | }
299 | .vjs-default-skin .vjs-volume-menu-button:hover .vjs-menu .vjs-menu-content,
300 | .vjs-default-skin .vjs-volume-menu-button .vjs-menu.vjs-lock-showing .vjs-menu-content {
301 | height: 2.9em;
302 | width: 10em;
303 | }
304 | /* Progress
305 | --------------------------------------------------------------------------------
306 | */
307 | .vjs-default-skin .vjs-progress-control {
308 | position: absolute;
309 | left: 0;
310 | right: 0;
311 | width: auto;
312 | font-size: 0.3em;
313 | height: 1em;
314 | /* Set above the rest of the controls. */
315 | top: -1em;
316 | /* Shrink the bar slower than it grows. */
317 | /* transition */
318 | -webkit-transition: all 0.4s;
319 | -moz-transition: all 0.4s;
320 | -o-transition: all 0.4s;
321 | transition: all 0.4s;
322 | }
323 | /* On hover, make the progress bar grow to something that's more clickable.
324 | This simply changes the overall font for the progress bar, and this
325 | updates both the em-based widths and heights, as wells as the icon font */
326 | .vjs-default-skin:hover .vjs-progress-control {
327 | font-size: .9em;
328 | /* Even though we're not changing the top/height, we need to include them in
329 | the transition so they're handled correctly. */
330 |
331 | /* transition */
332 | -webkit-transition: all 0.2s;
333 | -moz-transition: all 0.2s;
334 | -o-transition: all 0.2s;
335 | transition: all 0.2s;
336 | }
337 | /* Box containing play and load progresses. Also acts as seek scrubber. */
338 | .vjs-default-skin .vjs-progress-holder {
339 | height: 100%;
340 | }
341 | /* Progress Bars */
342 | .vjs-default-skin .vjs-progress-holder .vjs-play-progress,
343 | .vjs-default-skin .vjs-progress-holder .vjs-load-progress,
344 | .vjs-default-skin .vjs-progress-holder .vjs-load-progress div {
345 | position: absolute;
346 | display: block;
347 | height: 100%;
348 | margin: 0;
349 | padding: 0;
350 | /* updated by javascript during playback */
351 |
352 | width: 0;
353 | /* Needed for IE6 */
354 | left: 0;
355 | top: 0;
356 | }
357 | .vjs-default-skin .vjs-play-progress {
358 | /*
359 | Using a data URI to create the white diagonal lines with a transparent
360 | background. Surprisingly works in IE8.
361 | Created using http://www.patternify.com
362 | Changing the first color value will change the bar color.
363 | Also using a paralax effect to make the lines move backwards.
364 | The -50% left position makes that happen.
365 | */
366 |
367 | background: #66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat;
368 | }
369 | .vjs-default-skin .vjs-load-progress {
370 | background: #646464 /* IE8- Fallback */;
371 | background: rgba(255, 255, 255, 0.2);
372 | }
373 | /* there are child elements of the load progress bar that represent the
374 | specific time ranges that have been buffered */
375 | .vjs-default-skin .vjs-load-progress div {
376 | background: #787878 /* IE8- Fallback */;
377 | background: rgba(255, 255, 255, 0.1);
378 | }
379 | .vjs-default-skin .vjs-seek-handle {
380 | width: 1.5em;
381 | height: 100%;
382 | }
383 | .vjs-default-skin .vjs-seek-handle:before {
384 | padding-top: 0.1em /* Minor adjustment */;
385 | }
386 | /* Live Mode
387 | --------------------------------------------------------------------------------
388 | */
389 | .vjs-default-skin.vjs-live .vjs-time-controls,
390 | .vjs-default-skin.vjs-live .vjs-time-divider,
391 | .vjs-default-skin.vjs-live .vjs-progress-control {
392 | display: none;
393 | }
394 | .vjs-default-skin.vjs-live .vjs-live-display {
395 | display: block;
396 | }
397 | /* Live Display
398 | --------------------------------------------------------------------------------
399 | */
400 | .vjs-default-skin .vjs-live-display {
401 | display: none;
402 | font-size: 1em;
403 | line-height: 3em;
404 | }
405 | /* Time Display
406 | --------------------------------------------------------------------------------
407 | */
408 | .vjs-default-skin .vjs-time-controls {
409 | font-size: 1em;
410 | /* Align vertically by making the line height the same as the control bar */
411 | line-height: 3em;
412 | }
413 | .vjs-default-skin .vjs-current-time {
414 | float: left;
415 | }
416 | .vjs-default-skin .vjs-duration {
417 | float: left;
418 | }
419 | /* Remaining time is in the HTML, but not included in default design */
420 | .vjs-default-skin .vjs-remaining-time {
421 | display: none;
422 | float: left;
423 | }
424 | .vjs-time-divider {
425 | float: left;
426 | line-height: 3em;
427 | }
428 | /* Fullscreen
429 | --------------------------------------------------------------------------------
430 | */
431 | .vjs-default-skin .vjs-fullscreen-control {
432 | width: 3.8em;
433 | cursor: pointer;
434 | float: right;
435 | }
436 | .vjs-default-skin .vjs-fullscreen-control:before {
437 | content: "\e000";
438 | }
439 | /* Switch to the exit icon when the player is in fullscreen */
440 | .vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control:before {
441 | content: "\e00b";
442 | }
443 | /* Big Play Button (play button at start)
444 | --------------------------------------------------------------------------------
445 | Positioning of the play button in the center or other corners can be done more
446 | easily in the skin designer. http://designer.videojs.com/
447 | */
448 | .vjs-default-skin .vjs-big-play-button {
449 | left: 0.5em;
450 | top: 0.5em;
451 | font-size: 3em;
452 | display: block;
453 | z-index: 2;
454 | position: absolute;
455 | width: 4em;
456 | height: 2.6em;
457 | text-align: center;
458 | vertical-align: middle;
459 | cursor: pointer;
460 | opacity: 1;
461 | /* Need a slightly gray bg so it can be seen on black backgrounds */
462 | /* background-color-with-alpha */
463 | background-color: #07141e;
464 | background-color: rgba(7, 20, 30, 0.7);
465 | border: 0.1em solid #3b4249;
466 | /* border-radius */
467 | -webkit-border-radius: 0.8em;
468 | -moz-border-radius: 0.8em;
469 | border-radius: 0.8em;
470 | /* box-shadow */
471 | -webkit-box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);
472 | -moz-box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);
473 | box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);
474 | /* transition */
475 | -webkit-transition: all 0.4s;
476 | -moz-transition: all 0.4s;
477 | -o-transition: all 0.4s;
478 | transition: all 0.4s;
479 | }
480 | /* Optionally center */
481 | .vjs-default-skin.vjs-big-play-centered .vjs-big-play-button {
482 | /* Center it horizontally */
483 | left: 50%;
484 | margin-left: -2.1em;
485 | /* Center it vertically */
486 | top: 50%;
487 | margin-top: -1.4000000000000001em;
488 | }
489 | /* Hide if controls are disabled */
490 | .vjs-default-skin.vjs-controls-disabled .vjs-big-play-button {
491 | display: none;
492 | }
493 | /* Hide when video starts playing */
494 | .vjs-default-skin.vjs-has-started .vjs-big-play-button {
495 | display: none;
496 | }
497 | /* Hide on mobile devices. Remove when we stop using native controls
498 | by default on mobile */
499 | .vjs-default-skin.vjs-using-native-controls .vjs-big-play-button {
500 | display: none;
501 | }
502 | .vjs-default-skin:hover .vjs-big-play-button,
503 | .vjs-default-skin .vjs-big-play-button:focus {
504 | outline: 0;
505 | border-color: #fff;
506 | /* IE8 needs a non-glow hover state */
507 | background-color: #505050;
508 | background-color: rgba(50, 50, 50, 0.75);
509 | /* box-shadow */
510 | -webkit-box-shadow: 0 0 3em #ffffff;
511 | -moz-box-shadow: 0 0 3em #ffffff;
512 | box-shadow: 0 0 3em #ffffff;
513 | /* transition */
514 | -webkit-transition: all 0s;
515 | -moz-transition: all 0s;
516 | -o-transition: all 0s;
517 | transition: all 0s;
518 | }
519 | .vjs-default-skin .vjs-big-play-button:before {
520 | content: "\e001";
521 | font-family: VideoJS;
522 | /* In order to center the play icon vertically we need to set the line height
523 | to the same as the button height */
524 |
525 | line-height: 2.6em;
526 | text-shadow: 0.05em 0.05em 0.1em #000;
527 | text-align: center /* Needed for IE8 */;
528 | position: absolute;
529 | left: 0;
530 | width: 100%;
531 | height: 100%;
532 | }
533 | .vjs-error .vjs-big-play-button {
534 | display: none;
535 | }
536 | /* Error Display
537 | --------------------------------------------------------------------------------
538 | */
539 | .vjs-error-display {
540 | display: none;
541 | }
542 | .vjs-error .vjs-error-display {
543 | display: block;
544 | position: absolute;
545 | left: 0;
546 | top: 0;
547 | width: 100%;
548 | height: 100%;
549 | }
550 | .vjs-error .vjs-error-display:before {
551 | content: 'X';
552 | font-family: Arial;
553 | font-size: 4em;
554 | color: #666666;
555 | /* In order to center the play icon vertically we need to set the line height
556 | to the same as the button height */
557 |
558 | line-height: 1;
559 | text-shadow: 0.05em 0.05em 0.1em #000;
560 | text-align: center /* Needed for IE8 */;
561 | vertical-align: middle;
562 | position: absolute;
563 | left: 0;
564 | top: 50%;
565 | margin-top: -0.5em;
566 | width: 100%;
567 | }
568 | .vjs-error-display div {
569 | position: absolute;
570 | bottom: 1em;
571 | right: 0;
572 | left: 0;
573 | font-size: 1.4em;
574 | text-align: center;
575 | padding: 3px;
576 | background: #000000;
577 | background: rgba(0, 0, 0, 0.5);
578 | }
579 | .vjs-error-display a,
580 | .vjs-error-display a:visited {
581 | color: #F4A460;
582 | }
583 | /* Loading Spinner
584 | --------------------------------------------------------------------------------
585 | */
586 | .vjs-loading-spinner {
587 | /* Should be hidden by default */
588 | display: none;
589 | position: absolute;
590 | top: 50%;
591 | left: 50%;
592 | font-size: 4em;
593 | line-height: 1;
594 | width: 1em;
595 | height: 1em;
596 | margin-left: -0.5em;
597 | margin-top: -0.5em;
598 | opacity: 0.75;
599 | }
600 | /* Show the spinner when waiting for data and seeking to a new time */
601 | .vjs-waiting .vjs-loading-spinner,
602 | .vjs-seeking .vjs-loading-spinner {
603 | display: block;
604 | /* only animate when showing because it can be processor heavy */
605 | /* animation */
606 | -webkit-animation: spin 1.5s infinite linear;
607 | -moz-animation: spin 1.5s infinite linear;
608 | -o-animation: spin 1.5s infinite linear;
609 | animation: spin 1.5s infinite linear;
610 | }
611 | /* Errors are unrecoverable without user interaction so hide the spinner */
612 | .vjs-error .vjs-loading-spinner {
613 | display: none;
614 | /* ensure animation doesn't continue while hidden */
615 | /* animation */
616 | -webkit-animation: none;
617 | -moz-animation: none;
618 | -o-animation: none;
619 | animation: none;
620 | }
621 | .vjs-default-skin .vjs-loading-spinner:before {
622 | content: "\e01e";
623 | font-family: VideoJS;
624 | position: absolute;
625 | top: 0;
626 | left: 0;
627 | width: 1em;
628 | height: 1em;
629 | text-align: center;
630 | text-shadow: 0em 0em 0.1em #000;
631 | }
632 | @-moz-keyframes spin {
633 | 0% {
634 | -moz-transform: rotate(0deg);
635 | }
636 | 100% {
637 | -moz-transform: rotate(359deg);
638 | }
639 | }
640 | @-webkit-keyframes spin {
641 | 0% {
642 | -webkit-transform: rotate(0deg);
643 | }
644 | 100% {
645 | -webkit-transform: rotate(359deg);
646 | }
647 | }
648 | @-o-keyframes spin {
649 | 0% {
650 | -o-transform: rotate(0deg);
651 | }
652 | 100% {
653 | -o-transform: rotate(359deg);
654 | }
655 | }
656 | @keyframes spin {
657 | 0% {
658 | transform: rotate(0deg);
659 | }
660 | 100% {
661 | transform: rotate(359deg);
662 | }
663 | }
664 | /* Menu Buttons (Captions/Subtitles/etc.)
665 | --------------------------------------------------------------------------------
666 | */
667 | .vjs-default-skin .vjs-menu-button {
668 | float: right;
669 | cursor: pointer;
670 | }
671 | .vjs-default-skin .vjs-menu {
672 | display: none;
673 | position: absolute;
674 | bottom: 0;
675 | left: 0em;
676 | /* (Width of vjs-menu - width of button) / 2 */
677 |
678 | width: 0em;
679 | height: 0em;
680 | margin-bottom: 3em;
681 | border-left: 2em solid transparent;
682 | border-right: 2em solid transparent;
683 | border-top: 1.55em solid #000000;
684 | /* Same width top as ul bottom */
685 |
686 | border-top-color: rgba(7, 40, 50, 0.5);
687 | /* Same as ul background */
688 |
689 | }
690 | /* Button Pop-up Menu */
691 | .vjs-default-skin .vjs-menu-button .vjs-menu .vjs-menu-content {
692 | display: block;
693 | padding: 0;
694 | margin: 0;
695 | position: absolute;
696 | width: 10em;
697 | bottom: 1.5em;
698 | /* Same bottom as vjs-menu border-top */
699 |
700 | max-height: 15em;
701 | overflow: auto;
702 | left: -5em;
703 | /* Width of menu - width of button / 2 */
704 |
705 | /* background-color-with-alpha */
706 | background-color: #07141e;
707 | background-color: rgba(7, 20, 30, 0.7);
708 | /* box-shadow */
709 | -webkit-box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);
710 | -moz-box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);
711 | box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);
712 | }
713 | .vjs-default-skin .vjs-menu-button:hover .vjs-control-content .vjs-menu,
714 | .vjs-default-skin .vjs-control-content .vjs-menu.vjs-lock-showing {
715 | display: block;
716 | }
717 | /* prevent menus from opening while scrubbing (FF, IE) */
718 | .vjs-default-skin.vjs-scrubbing .vjs-menu-button:hover .vjs-control-content .vjs-menu {
719 | display: none;
720 | }
721 | .vjs-default-skin .vjs-menu-button ul li {
722 | list-style: none;
723 | margin: 0;
724 | padding: 0.3em 0 0.3em 0;
725 | line-height: 1.4em;
726 | font-size: 1.2em;
727 | text-align: center;
728 | text-transform: lowercase;
729 | }
730 | .vjs-default-skin .vjs-menu-button ul li.vjs-selected {
731 | background-color: #000;
732 | }
733 | .vjs-default-skin .vjs-menu-button ul li:focus,
734 | .vjs-default-skin .vjs-menu-button ul li:hover,
735 | .vjs-default-skin .vjs-menu-button ul li.vjs-selected:focus,
736 | .vjs-default-skin .vjs-menu-button ul li.vjs-selected:hover {
737 | outline: 0;
738 | color: #111;
739 | /* background-color-with-alpha */
740 | background-color: #ffffff;
741 | background-color: rgba(255, 255, 255, 0.75);
742 | /* box-shadow */
743 | -webkit-box-shadow: 0 0 1em #ffffff;
744 | -moz-box-shadow: 0 0 1em #ffffff;
745 | box-shadow: 0 0 1em #ffffff;
746 | }
747 | .vjs-default-skin .vjs-menu-button ul li.vjs-menu-title {
748 | text-align: center;
749 | text-transform: uppercase;
750 | font-size: 1em;
751 | line-height: 2em;
752 | padding: 0;
753 | margin: 0 0 0.3em 0;
754 | font-weight: bold;
755 | cursor: default;
756 | }
757 | /* Subtitles Button */
758 | .vjs-default-skin .vjs-subtitles-button:before {
759 | content: "\e00c";
760 | }
761 | /* Captions Button */
762 | .vjs-default-skin .vjs-captions-button:before {
763 | content: "\e008";
764 | }
765 | /* Chapters Button */
766 | .vjs-default-skin .vjs-chapters-button:before {
767 | content: "\e00c";
768 | }
769 | .vjs-default-skin .vjs-chapters-button.vjs-menu-button .vjs-menu .vjs-menu-content {
770 | width: 24em;
771 | left: -12em;
772 | }
773 | /* Replacement for focus outline */
774 | .vjs-default-skin .vjs-captions-button:focus .vjs-control-content:before,
775 | .vjs-default-skin .vjs-captions-button:hover .vjs-control-content:before {
776 | /* box-shadow */
777 | -webkit-box-shadow: 0 0 1em #ffffff;
778 | -moz-box-shadow: 0 0 1em #ffffff;
779 | box-shadow: 0 0 1em #ffffff;
780 | }
781 | /*
782 | REQUIRED STYLES (be careful overriding)
783 | ================================================================================
784 | When loading the player, the video tag is replaced with a DIV,
785 | that will hold the video tag or object tag for other playback methods.
786 | The div contains the video playback element (Flash or HTML5) and controls,
787 | and sets the width and height of the video.
788 |
789 | ** If you want to add some kind of border/padding (e.g. a frame), or special
790 | positioning, use another containing element. Otherwise you risk messing up
791 | control positioning and full window mode. **
792 | */
793 | .video-js {
794 | background-color: #000;
795 | position: relative;
796 | padding: 0;
797 | /* Start with 10px for base font size so other dimensions can be em based and
798 | easily calculable. */
799 |
800 | font-size: 10px;
801 | /* Allow poster to be vertically aligned. */
802 |
803 | vertical-align: middle;
804 | /* display: table-cell; */
805 | /*This works in Safari but not Firefox.*/
806 |
807 | /* Provide some basic defaults for fonts */
808 |
809 | font-weight: normal;
810 | font-style: normal;
811 | /* Avoiding helvetica: issue #376 */
812 |
813 | font-family: Arial, sans-serif;
814 | /* Turn off user selection (text highlighting) by default.
815 | The majority of player components will not be text blocks.
816 | Text areas will need to turn user selection back on. */
817 |
818 | /* user-select */
819 | -webkit-user-select: none;
820 | -moz-user-select: none;
821 | -ms-user-select: none;
822 | user-select: none;
823 | }
824 | /* Playback technology elements expand to the width/height of the containing div
825 | or */
826 | .video-js .vjs-tech {
827 | position: absolute;
828 | top: 0;
829 | left: 0;
830 | width: 100%;
831 | height: 100%;
832 | }
833 | /* Fix for Firefox 9 fullscreen (only if it is enabled). Not needed when
834 | checking fullScreenEnabled. */
835 | .video-js:-moz-full-screen {
836 | position: absolute;
837 | }
838 | /* Fullscreen Styles */
839 | body.vjs-full-window {
840 | padding: 0;
841 | margin: 0;
842 | height: 100%;
843 | /* Fix for IE6 full-window. http://www.cssplay.co.uk/layouts/fixed.html */
844 | overflow-y: auto;
845 | }
846 | .video-js.vjs-fullscreen {
847 | position: fixed;
848 | overflow: hidden;
849 | z-index: 1000;
850 | left: 0;
851 | top: 0;
852 | bottom: 0;
853 | right: 0;
854 | width: 100% !important;
855 | height: 100% !important;
856 | /* IE6 full-window (underscore hack) */
857 | _position: absolute;
858 | }
859 | .video-js:-webkit-full-screen {
860 | width: 100% !important;
861 | height: 100% !important;
862 | }
863 | .video-js.vjs-fullscreen.vjs-user-inactive {
864 | cursor: none;
865 | }
866 | /* Poster Styles */
867 | .vjs-poster {
868 | background-repeat: no-repeat;
869 | background-position: 50% 50%;
870 | background-size: contain;
871 | cursor: pointer;
872 | margin: 0;
873 | padding: 0;
874 | position: absolute;
875 | top: 0;
876 | right: 0;
877 | bottom: 0;
878 | left: 0;
879 | }
880 | .vjs-poster img {
881 | display: block;
882 | margin: 0 auto;
883 | max-height: 100%;
884 | padding: 0;
885 | width: 100%;
886 | }
887 | /* Hide the poster after the video has started playing */
888 | .video-js.vjs-has-started .vjs-poster {
889 | display: none;
890 | }
891 | /* Don't hide the poster if we're playing audio */
892 | .video-js.vjs-audio.vjs-has-started .vjs-poster {
893 | display: block;
894 | }
895 | /* Hide the poster when controls are disabled because it's clickable
896 | and the native poster can take over */
897 | .video-js.vjs-controls-disabled .vjs-poster {
898 | display: none;
899 | }
900 | /* Hide the poster when native controls are used otherwise it covers them */
901 | .video-js.vjs-using-native-controls .vjs-poster {
902 | display: none;
903 | }
904 | /* Text Track Styles */
905 | /* Overall track holder for both captions and subtitles */
906 | .video-js .vjs-text-track-display {
907 | position: absolute;
908 | top: 0;
909 | left: 0;
910 | bottom: 3em;
911 | right: 0;
912 | pointer-events: none;
913 | }
914 | /* Captions Settings Dialog */
915 | .vjs-caption-settings {
916 | position: relative;
917 | top: 1em;
918 | background-color: #000;
919 | opacity: 0.75;
920 | color: #FFF;
921 | margin: 0 auto;
922 | padding: 0.5em;
923 | height: 15em;
924 | font-family: Arial, Helvetica, sans-serif;
925 | font-size: 12px;
926 | width: 40em;
927 | }
928 | .vjs-caption-settings .vjs-tracksettings {
929 | top: 0;
930 | bottom: 2em;
931 | left: 0;
932 | right: 0;
933 | position: absolute;
934 | overflow: auto;
935 | }
936 | .vjs-caption-settings .vjs-tracksettings-colors,
937 | .vjs-caption-settings .vjs-tracksettings-font {
938 | float: left;
939 | }
940 | .vjs-caption-settings .vjs-tracksettings-colors:after,
941 | .vjs-caption-settings .vjs-tracksettings-font:after,
942 | .vjs-caption-settings .vjs-tracksettings-controls:after {
943 | clear: both;
944 | }
945 | .vjs-caption-settings .vjs-tracksettings-controls {
946 | position: absolute;
947 | bottom: 1em;
948 | right: 1em;
949 | }
950 | .vjs-caption-settings .vjs-tracksetting {
951 | margin: 5px;
952 | padding: 3px;
953 | min-height: 40px;
954 | }
955 | .vjs-caption-settings .vjs-tracksetting label {
956 | display: block;
957 | width: 100px;
958 | margin-bottom: 5px;
959 | }
960 | .vjs-caption-settings .vjs-tracksetting span {
961 | display: inline;
962 | margin-left: 5px;
963 | }
964 | .vjs-caption-settings .vjs-tracksetting > div {
965 | margin-bottom: 5px;
966 | min-height: 20px;
967 | }
968 | .vjs-caption-settings .vjs-tracksetting > div:last-child {
969 | margin-bottom: 0;
970 | padding-bottom: 0;
971 | min-height: 0;
972 | }
973 | .vjs-caption-settings label > input {
974 | margin-right: 10px;
975 | }
976 | .vjs-caption-settings input[type="button"] {
977 | width: 40px;
978 | height: 40px;
979 | }
980 | /* Hide disabled or unsupported controls */
981 | .vjs-hidden {
982 | display: none !important;
983 | }
984 | .vjs-lock-showing {
985 | display: block !important;
986 | opacity: 1;
987 | visibility: visible;
988 | }
989 | /* In IE8 w/ no JavaScript (no HTML5 shim), the video tag doesn't register.
990 | The .video-js classname on the video tag also isn't considered.
991 | This optional paragraph inside the video tag can provide a message to users
992 | about what's required to play video. */
993 | .vjs-no-js {
994 | padding: 2em;
995 | color: #ccc;
996 | background-color: #333;
997 | font-size: 1.8em;
998 | font-family: Arial, sans-serif;
999 | text-align: center;
1000 | width: 30em;
1001 | height: 15em;
1002 | margin: 0 auto;
1003 | }
1004 | .vjs-no-js a,
1005 | .vjs-no-js a:visited {
1006 | color: #F4A460;
1007 | }
1008 | /* -----------------------------------------------------------------------------
1009 | The original source of this file lives at
1010 | https://github.com/videojs/video.js/blob/master/src/css/video-js.less */
1011 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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";
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 |
--------------------------------------------------------------------------------
/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}
--------------------------------------------------------------------------------
/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",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);
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/screenshots/chromecast-player.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kim-company/videojs-chromecast/72b141247ad83230e602c199d76475bdeb22f179/screenshots/chromecast-player.jpg
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/src/videojs.chromecast.coffee:
--------------------------------------------------------------------------------
1 | vjs.plugin "chromecast", (options) ->
2 | @chromecastComponent = new vjs.ChromecastComponent(@, options)
3 | @controlBar.addChild @chromecastComponent
4 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------