├── .gitignore ├── bower.json ├── package.json ├── LICENSE ├── videojs.endcard.css ├── videojs.endcard.less ├── example.html ├── README.md └── videojs.endcard.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-endcard", 3 | "version": "0.1.0", 4 | "main": "videojs.endcard.js", 5 | "description": "Simple, customizable end card solution for VideoJS.", 6 | "keywords": [ 7 | "videojs", 8 | "endcard" 9 | ], 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "components", 14 | "bower_components", 15 | "test", 16 | "tests" 17 | ], 18 | "dependencies": { 19 | "videojs": "~4.4.2" 20 | } 21 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-endcard", 3 | "version": "0.1.0", 4 | "description": "Simple, customizable end card solution for VideoJS.", 5 | "main": "videojs.endcard.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/theonion/videojs-endcard.git" 9 | }, 10 | "homepage": "http://theonion.github.io/videojs-endcard/", 11 | "keywords": [ 12 | "videojs", 13 | "endcard" 14 | ], 15 | "author": { 16 | "name": "Shaker Islam", 17 | "email": "sislam@theonion.com" 18 | }, 19 | "license": "MIT" 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 The Onion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /videojs.endcard.css: -------------------------------------------------------------------------------- 1 | .vjs-control-bar { 2 | z-index: 1; 3 | } 4 | 5 | .video-js #player-endcard { 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | bottom: 0; 10 | right: 0; 11 | background-color: #000; 12 | z-index: 0; 13 | } 14 | 15 | .video-js #player-endcard #related-content { 16 | position: absolute; 17 | top: 0; 18 | bottom: 3.5em; 19 | right: 0; 20 | left: 25%; 21 | } 22 | 23 | .video-js #player-endcard #related-content div { 24 | height: 50%; 25 | border: 1px solid gray; 26 | } 27 | 28 | .video-js #player-endcard #related-content div p { 29 | margin: 0; 30 | color: white; 31 | font-size: 20px; 32 | font-family: Helvetica; 33 | height: 100%; 34 | } 35 | 36 | .video-js #player-endcard #next-video { 37 | position: absolute; 38 | top: 0; 39 | bottom: 3.5em; 40 | right: 75%; 41 | left: 0%; 42 | } 43 | 44 | .video-js #player-endcard #next-video div { 45 | height: 90%; 46 | } 47 | 48 | .video-js #player-endcard #next-video div#countdown { 49 | height: 10%; 50 | font-family: Helvetica; 51 | font-size: 12px; 52 | color: red; 53 | } 54 | 55 | .video-js #player-endcard #next-video div p { 56 | margin: 0; 57 | color: white; 58 | font-size: 20px; 59 | font-family: Helvetica; 60 | height: 100%; 61 | } -------------------------------------------------------------------------------- /videojs.endcard.less: -------------------------------------------------------------------------------- 1 | .vjs-control-bar { 2 | z-index: 1; 3 | } 4 | .video-js { 5 | #player-endcard { 6 | position: absolute; 7 | top: 0; 8 | left: 0; 9 | bottom: 0; 10 | right: 0; 11 | background-color: #000; 12 | z-index: 0; 13 | #related-content { 14 | position: absolute; 15 | top: 0; 16 | bottom: 3.5em; 17 | right: 0; 18 | left: 25%; 19 | div { 20 | height: 50%; 21 | border: 1px solid gray; 22 | p { 23 | margin: 0; 24 | color: white; 25 | font-size: 20px; 26 | font-family: Helvetica; 27 | height: 100%; 28 | } 29 | } 30 | } 31 | #next-video { 32 | position: absolute; 33 | top: 0; 34 | bottom: 3.5em; 35 | right: 75%; 36 | left: 0%; 37 | div { 38 | height: 90%; 39 | p { 40 | margin: 0; 41 | color: white; 42 | font-size: 20px; 43 | font-family: Helvetica; 44 | height: 100%; 45 | } 46 | } 47 | div#countdown { 48 | height: 10%; 49 | font-family: Helvetica; 50 | font-size: 12px; 51 | color: red; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Video.js Endcard Example 5 | 6 | 7 | 14 | 15 | 16 |

Skip to the end of the video to see how the Endcard functions:

17 | 18 | 30 | 31 | 32 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## VideoJS End Cards 2 | 3 | Simple, customizable end card solution for VideoJS. 4 | 5 | 6 | 7 | This plugin adds an end card container, which shows/hides based on the VideoJS's `playing` and `ended` events. By implementing the `getRelatedContent` function, it can display thumbnails for content your users might be interested in, and you can implement the `getNextVid` function to drive your users to a page after `x` amount of seconds. 8 | 9 | The plugin is unopinionated on what kind of markup your thumbnails have - it just takes whatever you give it and appends it to the corresponding container div. 10 | 11 | Fork the repo and take a look! 12 | 13 | ## Example 14 | ```js 15 | 16 | 17 | 43 | ``` 44 | 45 | Also, `example.html` is a more detailed demo. 46 | 47 | 48 | ## Styling 49 | 50 | Check out `videojs.endcard.less` for ideas on how to style the endcard. The default id's are in the options. 51 | 52 | 53 | ## Options 54 | 55 | **Functions**: 56 | 57 | `getRelatedContent` : **Required** for display of related content. It must invoke the callback function, which takes an **array of DOM Elements** and appends them to the "related-content" container. 58 | 59 | `getNextVid` : **Required** for autoplaying video. It must invoke the callback function, which takes a **single DOM Element** and appends it to the "next-video" container. When the countdown ends, the user is taken to the **first** anchor href that is found in the DOM Element passed to the callback function. 60 | 61 | **Styling Stuff**: 62 | * `endcard` : id for the end card. Default = "player-endcard" 63 | * `related` : id for div containing related content thumbnails. Default = "related-content" 64 | * `next` : id for div containing the thumbnail for the upcoming video. Default = "next-video" 65 | * `count` : number of seconds until the next video. Default = 10 66 | * `counter` : id for the countdown number. Default = "counter" 67 | * `countdown` : id for the div containing the countdown. Default = "countdown" 68 | * `countdown_text` : text displayed for the countdown. Default = "Next video in:" 69 | -------------------------------------------------------------------------------- /videojs.endcard.js: -------------------------------------------------------------------------------- 1 | (function(vjs) { 2 | "use strict"; 3 | var 4 | extend = function(obj) { 5 | var arg, i, k; 6 | for (i = 1; i < arguments.length; i++) { 7 | arg = arguments[i]; 8 | for (k in arg) { 9 | if (arg.hasOwnProperty(k)) { 10 | obj[k] = arg[k]; 11 | } 12 | } 13 | } 14 | return obj; 15 | }, 16 | 17 | defaults = { 18 | count: 10, 19 | counter: "counter", 20 | countdown: "countdown", 21 | countdown_text: "Next video in:", 22 | endcard: "player-endcard", 23 | related: "related-content", 24 | next: "next-video", 25 | getRelatedContent: function(callback){callback();}, 26 | getNextVid: function(callback){callback();} 27 | }, 28 | 29 | endcard = function(options) { 30 | var player = this; 31 | var el = this.el(); 32 | var settings = extend({}, defaults, options || {}); 33 | 34 | // set background 35 | var card = document.createElement('div'); 36 | card.id = settings.endcard; 37 | card.style.display = 'none'; 38 | 39 | el.appendChild(card); 40 | 41 | settings.getRelatedContent(function(content) { 42 | if (content instanceof Array) { 43 | var related_content_div = document.createElement('div'); 44 | related_content_div.id = settings.related; 45 | 46 | for (var i = 0; i < content.length; i++) { 47 | related_content_div.appendChild(content[i]); 48 | } 49 | 50 | card.appendChild(related_content_div); 51 | } 52 | else { 53 | throw new TypeError("options.getRelatedContent must return an array"); 54 | } 55 | }); 56 | 57 | settings.getNextVid(function(next) { 58 | if (typeof next !== "undefined") { 59 | var next_div = document.createElement('div'); 60 | var counter = document.createElement('span'); 61 | var countdown = document.createElement('div'); 62 | counter.id = settings.counter; 63 | countdown.id = settings.countdown; 64 | next_div.id = settings.next; 65 | 66 | countdown.innerHTML = settings.countdown_text; 67 | countdown.appendChild(counter); 68 | next_div.appendChild(countdown); 69 | next_div.appendChild(next); 70 | 71 | card.appendChild(next_div); 72 | } 73 | }); 74 | 75 | var counter_started = 0; 76 | player.on('ended', function() { 77 | card.style.display = 'block'; 78 | var next = document.getElementById(settings.next); 79 | if (next !== null) { 80 | var href = next.getElementsByTagName("a")[0].href; 81 | var count = settings.count; 82 | counter.innerHTML = count; 83 | 84 | var interval = setInterval(function(){ 85 | count--; 86 | if (count <= 0) { 87 | clearInterval(interval); 88 | window.location = href; 89 | return; 90 | } 91 | counter.innerHTML = count; 92 | }, 1000); 93 | } 94 | if (counter_started === 0) { 95 | counter_started++; 96 | player.on('playing', function() { 97 | card.style.display = 'none'; 98 | clearInterval(interval); 99 | }); 100 | } 101 | }); 102 | 103 | 104 | 105 | }; 106 | 107 | vjs.plugin('endcard', endcard); 108 | 109 | })(window.videojs); --------------------------------------------------------------------------------