├── .gitignore
├── README.md
├── bower.json
├── converter
├── nodejs
│ ├── app.js
│ └── package.json
├── nodejs_one
│ ├── app.js
│ └── package.json
└── php
│ └── to_data_uri.php
├── dist
├── frameplayer.min.css
└── frameplayer.min.js
├── gulpfile.js
├── index.html
├── package.json
├── run_server.py
├── src
├── frameplayer.css
└── frameplayer.js
├── videos
├── dragons.json
├── video01.json
└── video02.json
└── videos_src
└── dragons.mp4
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Frame Player
2 |
3 | A video player without video files, just JSON. Based on "images frames" thought to mobile devices!
4 |
5 | - Project Page: [http://vagnervjs.github.io/frame-player](http://vagnervjs.github.io/frame-player)
6 |
7 | ##The Problem
8 | The problem of today's HTML5 video is that it can't be played in-line on an HTML page on mobile devices. The way the handheld devices handle it is they open the video in a native player which takes over the page itself, making it impossible to implement any interaction while the video is playing. Also, you can't play two videos at the same time.
9 |
10 | ##The Solution
11 | Create a player instead of playing video files, show a sequence of images at a certain rate.
12 |
13 | ##Instalation
14 |
15 | - Download the [latest version](https://github.com/vagnervjs/frame-player/releases/) of Frame Player.
16 |
17 | - **OR** Use **bower** to install Frame Player
18 |
19 | ```bash
20 | bower install frame-player
21 | ```
22 |
23 | - Put the script and the style on your page
24 |
25 | ```html
26 |
27 |
28 | ```
29 |
30 | ##Usage
31 |
32 | - Insert this HTML code on any part of your page and set the data-src attribute for your JSON video file
33 |
34 | ```html
35 |
36 | ```
37 |
38 | - Set the options
39 |
40 | ```javascript
41 | var options = ({
42 | 'rate': 30,
43 | 'controls': false,
44 | 'autoplay': true,
45 | 'backwards': false,
46 | 'startFrame': 10,
47 | 'width': '640px',
48 | 'height': '390px',
49 | // 'radius': '50%'
50 | });
51 | ```
52 |
53 | - Init the player
54 |
55 | ```javascript
56 | var player = new FramePlayer('my-player', options);
57 | player.play();
58 | ```
59 |
60 | ### Methods
61 |
62 | Method | Parameters | Returns | Description
63 | --- | --- | --- | ---
64 | `play()` | None. | Nothing. | Start playing the video.
65 | `pause()` | None. | Nothing. | Pause the current video.
66 | `resume()` | None. | Nothing. | Play the current video from the moment it was paused.
67 | `gotoFrame()` | Integer. | Nothing. | Jumps to a specific frame of the video.
68 |
69 |
70 | ##Generating the JSON Video File (ffmpeg lib must be installed)
71 |
72 | - Option 1: Node.js - single command
73 |
74 | ```bash
75 | cd converter/nodejs_one
76 | node app.js path/to/video/file path/to/video.json/file startTime endTime
77 | ```
78 |
79 | - Option 2: Node.js
80 |
81 | - Use ffmpeg to generate the frames from a video file:
82 |
83 | ```bash
84 | ffmpeg -i video.mp4 -an -f image2 "%d.jpg"
85 | ```
86 |
87 | - Convert all frames on a single JSON file
88 |
89 | ```bash
90 | cd converter/nodejs
91 | node app.js frameStart frameEnd folder/to/imgs/ json/video.json
92 | ```
93 |
94 | - Option 3: PHP
95 |
96 | - Use ffmpeg to generate the frames from a video file:
97 |
98 | ```bash
99 | ffmpeg -i video.mp4 -an -f image2 "%d.jpg"
100 | ```
101 |
102 | ```bash
103 | cd converter/php
104 | php to_data_uri.php frameStart frameEnd folder/to/imgs/ json/video.json
105 | ```
106 |
107 | ## Development
108 |
109 | In order to run it locally you'll need to fetch some dependencies and a basic setup.
110 |
111 | 1. Install [Gulp](http://gulpjs.com/):
112 |
113 | ```sh
114 | $ [sudo] npm install --global gulp
115 | ```
116 |
117 | 2. Install local dependencies:
118 |
119 | ```sh
120 | $ npm install
121 | ```
122 |
123 | 3. To test your project, start the development server (using your prefered server) and open `http://localhost:8000`.
124 |
125 | ```sh
126 | $ python -m SimpleHTTPServer 8080
127 | ```
128 |
129 | 4. To build the distribution files before releasing a new version.
130 |
131 | ```sh
132 | $ gulp build
133 | ```
134 |
135 | 5. Send everything to `gh-pages` branch.
136 |
137 |
138 | ## Contributing
139 |
140 | 1. Fork it!
141 | 2. Create your feature branch: `git checkout -b my-new-feature`
142 | 3. Commit your changes: `git commit -m 'Add some feature'`
143 | 4. Push to the branch: `git push origin my-new-feature`
144 | 5. Submit a pull request :D
145 |
146 | ## Author
147 |
148 | [](http://vagnersantana.com) |
149 | --- | --- | --- | --- | --- | --- | ---
150 | [Vagner Santana](http://vagnersantana.com) [@vagnervjs](http://twitter.com/vagnervjs)|
151 |
152 |
153 |
154 |
155 | ## License
156 |
157 | - Code is under [MIT license](http://vagnersantana.mit-license.org) © Vagner Santana
158 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frame-player",
3 | "version": "0.2.0",
4 | "homepage": "https://github.com/vagnervjs/frame-player",
5 | "authors": [
6 | "Vagner Santana "
7 | ],
8 | "description": "A video player without video files, just JSON. Based on \"images frames\" thought to mobile devices!",
9 | "main": "src/js/frameplayer.js",
10 | "keywords": [
11 | "json",
12 | "player",
13 | "frame",
14 | "mobile"
15 | ],
16 | "license": "MIT",
17 | "ignore": [
18 | "**/.*",
19 | "node_modules",
20 | "bower_components",
21 | "test",
22 | "tests",
23 | "videos",
24 | "js",
25 | "css"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/converter/nodejs/app.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | util = require('util'),
3 | mime = require('mime'),
4 | path = require('path');
5 |
6 | function base64Img(src) {
7 | var data = fs.readFileSync(src).toString('base64');
8 | return util.format('data:%s;base64,%s', mime.lookup(src), data);
9 | }
10 |
11 | /*
12 | * Possible argument positions
13 | *
14 | * converter frameStart(number) frameEnd(number) folder(string) outputFile(string)
15 | * converter folder(string) outputFile(string)
16 | *
17 | */
18 |
19 | if(process.argv.length > 2) {
20 | // Check if the first argument is a number or string
21 | if(!isNaN(parseInt(process.argv[2]))) {
22 | var frameStart = process.argv[2],
23 | frameEnd = process.argv[3],
24 | folder = process.argv[4],
25 | outputFile = process.argv[5],
26 | dataUri = null,
27 | framesImg = [];
28 | } else {
29 | var frameStart = 1,
30 | frameEnd = fs.readdirSync(process.argv[2]).length,
31 | folder = process.argv[2],
32 | outputFile = process.argv[3];
33 | }
34 |
35 | var dataUri = null,
36 | framesImg = [],
37 | fileTypeExt = path.extname(fs.readdirSync(folder)[0]); //If there's a better way, please do it...
38 |
39 | for (; frameStart <= frameEnd; frameStart++) {
40 | console.log('Converting file: ' + frameStart);
41 | dataUri = base64Img(folder + frameStart + fileTypeExt);
42 | framesImg[frameStart] = dataUri;
43 | }
44 |
45 | framesImg.splice(null, 1);
46 |
47 | var json = {
48 | frames: framesImg
49 | };
50 |
51 | fs.writeFile(outputFile, JSON.stringify(json), function(err) {
52 | if(err) {
53 | console.log(err);
54 | } else {
55 | console.log('Completed!');
56 | }
57 | });
58 | } else {
59 | console.error('Not enough parameters supplied!');
60 | }
--------------------------------------------------------------------------------
/converter/nodejs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Frame-Player-Img-to-dataURI",
3 | "version": "0.0.1",
4 | "description": "Converts a sequence of images in dataURI base64",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/vagnervjs/frame-player"
12 | },
13 | "keywords": [
14 | "frame-player",
15 | "datauri",
16 | "base64"
17 | ],
18 | "author": "Vagner Santana",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/vagnervjs/frame-player/issues"
22 | },
23 | "devDependencies": {
24 | "mime": "^1.2.11",
25 | "path": "^0.4.9"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/converter/nodejs_one/app.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | util = require('util'),
3 | mime = require('mime'),
4 | path = require('path'),
5 | ffmpeg = require('fluent-ffmpeg'),
6 | mkdirp = require('mkdirp');
7 |
8 | /*
9 | * Possible argument positions
10 | *
11 | * converter pathVideo(string) pathVideoFrame(string) startVideoTime(number) endVideoTime(number)
12 | * converter pathVideo(string) pathVideoFrame(string) startVideoTime(number)
13 | * converter pathVideo(string) pathVideoFrame(string)
14 | *
15 | */
16 |
17 | if(process.argv.length > 3) {
18 | var pathVideo = process.argv[2],
19 | pathVideoFrame = process.argv[3],
20 | startVideoTime = parseInt(process.argv[4]),
21 | endVideoTime = parseInt(process.argv[5]),
22 | tempDirName = path.resolve('/tmp/', '.images');
23 | var json = {};
24 |
25 | Promise.resolve().then(function(){
26 | createOrCleanTempDir(tempDirName);
27 | }).
28 | then(function(){
29 | return createVideoFrames(pathVideo, startVideoTime, endVideoTime, tempDirName);
30 | }).
31 | then(function(){
32 | json.frames = generateStringVideoFrames(tempDirName);
33 | saveVideoFrame(pathVideoFrame, JSON.stringify(json));
34 | }).
35 | catch(function(err){
36 | console.log('Error -', err);
37 | })
38 | } else {
39 | console.error('Not enough parameters supplied!');
40 | }
41 |
42 | function createOrCleanTempDir(tempDirName){
43 | mkdirp(tempDirName, function(err){
44 | fs.readdirSync(tempDirName).map(function(elem, index){
45 | fs.unlinkSync(path.resolve(tempDirName, elem));
46 | });
47 | })
48 | }
49 |
50 | function createVideoFrames(_path, startTime, endTime, tempDirName){
51 | return promise = new Promise(function(resolve, reject){
52 | var command = ffmpeg(path.resolve(_path))
53 | .noAudio()
54 | .on('end', function(){
55 | resolve();
56 | })
57 | .on('error', function(err){
58 | reject(err);
59 | })
60 |
61 | if (startTime){
62 | command = command.seekInput(startTime);
63 | }
64 | if (endTime){
65 | if (startTime > endTime){
66 | reject('Start time must be lower than end time')
67 | }
68 | command = command.duration(endTime - startTime);
69 | }
70 |
71 | command.save(path.resolve(tempDirName, 'image-%07d.jpg'));
72 | });
73 | }
74 |
75 | function generateStringVideoFrames(tempDirName){
76 | return fs.readdirSync(tempDirName).sort().map(function(elem, index){
77 | return base64Img(path.resolve(tempDirName, elem));
78 | })
79 | }
80 |
81 | function base64Img(src) {
82 | var data = fs.readFileSync(src).toString('base64');
83 | return util.format('data:%s;base64,%s', mime.lookup(src), data);
84 | }
85 |
86 | function saveVideoFrame(_path, data){
87 | fs.writeFileSync(_path, data);
88 | }
89 |
--------------------------------------------------------------------------------
/converter/nodejs_one/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodejs_one",
3 | "version": "1.0.0",
4 | "description": "One command converter from video file to json base64 frames",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "AlexBazer",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "fluent-ffmpeg": "^2.0.1",
13 | "mime": "^1.3.4",
14 | "mkdirp": "^0.5.1",
15 | "path": "^0.12.7"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/converter/php/to_data_uri.php:
--------------------------------------------------------------------------------
1 | buffer(file_get_contents($image));
16 | return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($image));
17 | }
18 |
19 | $frameStart = $argv[1];
20 | $frameEnd = $argv[2];
21 | $folder = $argv[3];
22 | $outputFile = $argv[4];
23 |
24 | for ($i= $frameStart; $i < $frameEnd; $i++) {
25 | $image = $folder . $i . '.jpg';
26 | $frames[] = getDataURI($image);
27 | }
28 |
29 | $json['frames'] = $frames;
30 |
31 | $file = fopen($outputFile, 'w');
32 | fwrite($file, json_encode($json));
33 | fclose($file);
34 |
--------------------------------------------------------------------------------
/dist/frameplayer.min.css:
--------------------------------------------------------------------------------
1 | .frameplayer{background:#ccc;position:relative}.frameplayer canvas{width:100%;height:100%}.frameplayer>.fp-ctrl{position:absolute;bottom:0;left:0;margin:0;z-index:1;opacity:.6;height:40px;background:#000}.fp-ctrl>.fp-btn{margin:10px 10px 0;float:left}.fp-ctrl>.fp-select{margin-top:10px;float:left;text-transform:capitalize}input.to-frame{width:20px;margin-right:6px}label.to-frame{margin:10px 10px 0;float:left}.frameplayer>.fp-loading{text-align:center;line-height:15;font-size:16px;font-family:Helvetica,Arial,sans-serif;position:absolute;top:0;left:0;width:100%;height:100%}.fp-grayscale{-webkit-filter:grayscale(100%);filter:grayscale(100%)}.fp-sepia{-webkit-filter:sepia(100%);filter:sepia(100%)}.fp-invert{-webkit-filter:invert(100%);filter:invert(100%)}
--------------------------------------------------------------------------------
/dist/frameplayer.min.js:
--------------------------------------------------------------------------------
1 | var FramePlayer=function(e,t){this.divCont=document.getElementById(e),this.elem=e,this.jsonVideoSrc=this.divCont.getAttribute("data-vidsrc"),this.rate=20,this.controls=!0,this.paused=!1,this.width="480px",this.height="320px",this.backwards=!1,this.currentFrame=-1,this.startFrame=0,this.radius=null,this.setOptions(t),this.initializeRequestAnimationFrame(),this.img=document.createElement("img"),this.canvas=document.createElement("canvas"),this.context=this.canvas.getContext("2d"),this.divCont.appendChild(this.canvas)};FramePlayer.prototype.setOptions=function(e){if("rate"in e&&(this.rate=e.rate),"controls"in e&&(this.controls=e.controls),"autoplay"in e&&(e.autoplay||(this.paused=!0)),"width"in e&&(this.width=e.width),"height"in e&&(this.height=e.height),"startFrame"in e&&(this.startFrame=this.currentFrame=e.startFrame),"backwards"in e&&(this.backwards=e.backwards),"radius"in e){var t=document.createElement("style");t.setAttribute("id","style-"+this.elem),t.innerHTML="#"+this.elem+", .frames-"+this.elem+"{ border-radius: "+e.radius+"; overflow: hidden;}",document.head.appendChild(t)}this.divCont.style.width=this.width,this.divCont.style.height=this.height,this.controls&&this.createControlBar()},FramePlayer.prototype.render=function(e){var t,a,n=Date.now(),i=1e3/e.rate,r=e.jsonVideoFile.frames.length,s=function(){t=Date.now(),a=t-n,a>i&&(n=t-a%i,e.paused||(e.currentFrame=e.backwards?e.currentFrame-=1:e.currentFrame+=1,e.currentFrame>=r?e.currentFrame=0:e.currentFrame<0&&(e.currentFrame=r-1),e.drawFrame(e))),window.requestAnimationFrame(s)};window.requestAnimationFrame(s)},FramePlayer.prototype.drawFrame=function(e){e.img.src=e.jsonVideoFile.frames[e.currentFrame],e.context.drawImage(e.img,0,0,e.canvas.width,e.canvas.height)},FramePlayer.prototype.createControlBar=function(){var e=this,t=document.createElement("div");t.setAttribute("class","fp-ctrl"),t.style.width=this.width;var a=document.createElement("button");a.setAttribute("id","pause-"+e.elem),a.setAttribute("class","fp-btn"),a.innerHTML="Pause",a.addEventListener("click",function(){e.pause()},!1),t.appendChild(a);var n=document.createElement("button");n.setAttribute("id","play-"+e.elem),n.setAttribute("class","fp-btn"),n.innerHTML="Play",n.addEventListener("click",function(){e.resume()},!1),t.appendChild(n);var i=document.createElement("button");i.setAttribute("id","backwards-"+e.elem),i.setAttribute("class","fp-btn"),i.innerHTML="Backward",i.addEventListener("click",function(){e.reverse()},!1),t.appendChild(i),e.paused?a.style.display="none":n.style.display="none";for(var r=document.createElement("select"),s=["normal","grayscale","sepia","invert"],o=0,l=s.length;l>o;o++){var d=document.createElement("option");d.setAttribute("value",s[o]),d.innerHTML=s[o],r.appendChild(d)}r.setAttribute("id","filter-"+e.elem),r.setAttribute("class","fp-select"),r.addEventListener("change",function(){e.setFilter(this.value)},!1),t.appendChild(r);var c=document.createElement("label"),m=document.createElement("input"),u=document.createElement("input");c.className="to-frame",m.type="text",m.name="frame",m.value=e.startFrame,m.className="to-frame",e.toFrameInput=m,u.type="submit",u.value="go to frame",u.onclick=function(){value=parseInt(m.value,10),e.gotoFrame(value)},c.appendChild(m),c.appendChild(u),t.appendChild(c),this.divCont.appendChild(t)},FramePlayer.prototype.play=function(){this.getFile(this.jsonVideoSrc,function(e){e.paused?(e.render(e),e.drawFrame(e)):e.render(e)})},FramePlayer.prototype.resume=function(){var e=document.getElementById("play-"+this.elem),t=document.getElementById("pause-"+this.elem);e.style.display="none",t.style.display="block",this.paused=!1},FramePlayer.prototype.pause=function(){var e=document.getElementById("play-"+this.elem),t=document.getElementById("pause-"+this.elem);e.style.display="block",t.style.display="none",this.paused=!0},FramePlayer.prototype.reverse=function(){var e=document.getElementById("backwards-"+this.elem);this.backwards=!this.backwards,value=this.backwards?"Forward":"Backward",e.innerHTML=value},FramePlayer.prototype.gotoFrame=function(e){e===parseInt(e,10)&&(this.currentFrame=this.startFrame=this.toFrameInput.value=e,void 0===this.jsonVideoFile?this.play():this.drawFrame(this))},FramePlayer.prototype.setFilter=function(e){var t=document.querySelector("#"+this.elem+" canvas");switch(e){case"normal":t.setAttribute("class","");break;case"grayscale":t.setAttribute("class","fp-grayscale");break;case"sepia":t.setAttribute("class","fp-sepia");break;case"invert":t.setAttribute("class","fp-invert")}},FramePlayer.prototype.getFile=function(e,t){var a=new XMLHttpRequest,n=this,i=document.createElement("p");if(!a)throw"Error loading file.";a.open("GET",e,!0),a.setRequestHeader("Content-Type","application/json;charset=UTF-8"),a.send(null),a.onprogress=function(){i.innerHTML="Loading...",i.setAttribute("class","fp-loading"),n.divCont.appendChild(i)},void 0!==typeof a.onload?a.onload=function(){n.divCont.removeChild(i),n.jsonVideoFile=JSON.parse(this.responseText),t(n),a=null}:a.onreadystatechange=function(){4===a.readyState&&(n.divCont.removeChild(i),n.jsonVideoFile=JSON.parse(this.responseText),t(n),a=null)}},FramePlayer.prototype.initializeRequestAnimationFrame=function(){for(var e=0,t=["ms","moz","webkit","o"],a=0;a
2 |
3 |
4 |
5 | Frame Player
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frame-player",
3 | "version": "0.2.0",
4 | "description": "A video player without video files, just JSON. Based on \"images frames\" thought to mobile devices!",
5 | "main": "src/fameplayer.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/vagnervjs/frame-player.git"
12 | },
13 | "keywords": [
14 | "frame",
15 | "player",
16 | "frame-player",
17 | "video",
18 | "json",
19 | "images",
20 | "mobile"
21 | ],
22 | "author": "Vagner Santana",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/vagnervjs/frame-player/issues"
26 | },
27 | "homepage": "https://github.com/vagnervjs/frame-player",
28 | "devDependencies": {
29 | "del": "^1.1.1",
30 | "gulp": "^3.8.11",
31 | "gulp-concat": "^2.5.2",
32 | "gulp-cssmin": "^0.1.6",
33 | "gulp-uglify": "^1.1.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/run_server.py:
--------------------------------------------------------------------------------
1 | # run_server.py
2 | import SimpleHTTPServer
3 |
4 | m = SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map
5 | m[''] = 'text/plain'
6 | m.update(dict([(k, v + ';charset=UTF-8') for k, v in m.items()]))
7 |
8 | SimpleHTTPServer.test()
9 |
--------------------------------------------------------------------------------
/src/frameplayer.css:
--------------------------------------------------------------------------------
1 | /*
2 | frameplayer.css
3 | A video player without video files, just JSON. Based on "images frames" thought to mobile devices!
4 |
5 | @author: Vagner Santana
6 | @link: http://github.com/vagnervjs/frame-video
7 | @version: 0.2.0
8 | @since: 04/10/2013
9 | */
10 |
11 | /* Player */
12 | .frameplayer {
13 | background: #ccc;
14 | position: relative;
15 | }
16 |
17 | .frameplayer canvas {
18 | width: 100%;
19 | height: 100%;
20 | }
21 |
22 | /* Control Bar */
23 | .frameplayer > .fp-ctrl {
24 | position: absolute;
25 | bottom: 0;
26 | left: 0;
27 | margin: 0;
28 | z-index: 1;
29 | opacity: .6;
30 | height: 40px;
31 | background: #000;
32 | }
33 |
34 | /* Buttons */
35 | .fp-ctrl > .fp-btn {
36 | margin: 10px 10px 0 10px;
37 | float: left;
38 | }
39 |
40 | .fp-ctrl > .fp-select {
41 | margin-top: 10px;
42 | float: left;
43 | text-transform: capitalize;
44 | }
45 |
46 | input.to-frame {
47 | width: 20px;
48 | margin-right:6px;
49 | }
50 |
51 | label.to-frame {
52 | margin: 10px 10px 0 10px;
53 | float: left;
54 | }
55 |
56 | /* Loading Message */
57 | .frameplayer > .fp-loading {
58 | text-align: center;
59 | line-height: 15;
60 | font-size: 16px;
61 | font-family: Helvetica, Arial, sans-serif;
62 | position: absolute;
63 | top: 0;
64 | left: 0;
65 | width: 100%;
66 | height: 100%;
67 | }
68 |
69 | /* Filters */
70 | .fp-grayscale {
71 | -webkit-filter: grayscale(100%);
72 | filter: grayscale(100%);
73 | }
74 | .fp-sepia {
75 | -webkit-filter: sepia(100%);
76 | filter: sepia(100%);
77 | }
78 | .fp-invert {
79 | -webkit-filter: invert(100%);
80 | filter: invert(100%);
81 | }
82 |
--------------------------------------------------------------------------------
/src/frameplayer.js:
--------------------------------------------------------------------------------
1 | /*
2 | frameplayer.js
3 | A video player without video files, just JSON. Based on "images frames" thought to mobile devices!
4 |
5 | @author: Vagner Santana
6 | @link: http://github.com/vagnervjs/frame-video
7 | @version: 0.2.0
8 | @since: 04/10/2013
9 | */
10 |
11 | var FramePlayer = function(el, options) {
12 | this.divCont = document.getElementById(el);
13 | this.elem = el;
14 | this.jsonVideoSrc = this.divCont.getAttribute('data-vidsrc');
15 | this.rate = 20,
16 | this.controls = true,
17 | this.paused = false,
18 | this.width = '480px',
19 | this.height = '320px';
20 | this.backwards = false;
21 | this.currentFrame = -1;
22 | this.startFrame = 0
23 | this.radius = null;
24 |
25 | this.setOptions(options);
26 | this.initializeRequestAnimationFrame();
27 |
28 | this.img = document.createElement('img');
29 | this.canvas = document.createElement('canvas');
30 | this.context = this.canvas.getContext('2d');
31 | this.divCont.appendChild(this.canvas);
32 | };
33 |
34 | FramePlayer.prototype.setOptions = function(options) {
35 | if ('rate' in options) { this.rate = options.rate; }
36 | if ('controls' in options) { this.controls = options.controls;}
37 | if ('autoplay' in options) { if (!options.autoplay) { this.paused = true; } }
38 | if ('width' in options) { this.width = options.width; }
39 | if ('height' in options) { this.height = options.height; }
40 | if ('startFrame' in options) { this.startFrame = this.currentFrame = options.startFrame; }
41 | if ('backwards' in options) { this.backwards = options.backwards; }
42 | if ('radius' in options) {
43 | var currentStyle = document.createElement('style');
44 | currentStyle.setAttribute('id', 'style-' + this.elem);
45 | currentStyle.innerHTML = '#' + this.elem + ', .frames-' + this.elem + '{ border-radius: ' + options.radius + '; overflow: hidden;}';
46 | document.head.appendChild(currentStyle);
47 | }
48 |
49 | this.divCont.style.width = this.width;
50 | this.divCont.style.height = this.height;
51 |
52 | if(this.controls) {
53 | this.createControlBar();
54 | }
55 | };
56 |
57 | FramePlayer.prototype.render = function(player) {
58 |
59 | var now,
60 | then = Date.now(),
61 | interval = 1000/player.rate,
62 | delta,
63 | videoFramesNum = player.jsonVideoFile.frames.length;
64 |
65 | var processFrame = function() {
66 |
67 | now = Date.now();
68 | delta = now - then;
69 |
70 | if (delta > interval) {
71 | then = now - (delta % interval);
72 |
73 | if(!player.paused) {
74 |
75 | player.currentFrame = (player.backwards) ? player.currentFrame -= 1 : player.currentFrame += 1;
76 |
77 | if (player.currentFrame >= videoFramesNum) player.currentFrame = 0;
78 | else if (player.currentFrame < 0) player.currentFrame = videoFramesNum-1;
79 |
80 | player.drawFrame(player);
81 | }
82 | }
83 |
84 | window.requestAnimationFrame(processFrame);
85 | };
86 |
87 |
88 | window.requestAnimationFrame(processFrame);
89 | };
90 |
91 | FramePlayer.prototype.drawFrame = function(player) {
92 | player.img.src = player.jsonVideoFile.frames[player.currentFrame];
93 | player.context.drawImage(player.img, 0, 0, player.canvas.width, player.canvas.height);
94 | };
95 |
96 | FramePlayer.prototype.createControlBar = function() {
97 | var _self = this,
98 | controlBar = document.createElement('div');
99 | controlBar.setAttribute('class', 'fp-ctrl');
100 | controlBar.style.width = this.width;
101 |
102 | // Pause Button
103 | var btnPause = document.createElement('button');
104 | btnPause.setAttribute('id', 'pause-' + _self.elem);
105 | btnPause.setAttribute('class', 'fp-btn');
106 | btnPause.innerHTML = 'Pause';
107 | btnPause.addEventListener('click', function() {
108 | _self.pause();
109 | }, false
110 | );
111 | controlBar.appendChild(btnPause);
112 |
113 | // Play Button
114 | var btnPlay = document.createElement('button');
115 | btnPlay.setAttribute('id', 'play-' + _self.elem);
116 | btnPlay.setAttribute('class', 'fp-btn');
117 | btnPlay.innerHTML = 'Play';
118 | btnPlay.addEventListener('click', function() {
119 | _self.resume();
120 | }, false
121 | );
122 | controlBar.appendChild(btnPlay);
123 |
124 | // Backwards Button
125 | var btnBackwards = document.createElement('button');
126 | btnBackwards.setAttribute('id', 'backwards-' + _self.elem);
127 | btnBackwards.setAttribute('class', 'fp-btn');
128 | btnBackwards.innerHTML = 'Backward';
129 | btnBackwards.addEventListener('click', function() {
130 | _self.reverse()
131 | }, false
132 | );
133 | controlBar.appendChild(btnBackwards);
134 |
135 | // Display Play/Pause Button
136 | _self.paused ? btnPause.style.display = 'none' : btnPlay.style.display = 'none';
137 |
138 | // Filter Select
139 | var selectFilter = document.createElement('select'),
140 | options = ['normal', 'grayscale', 'sepia', 'invert'];
141 |
142 | for (var i = 0, t = options.length; i < t; i++) {
143 | var $option = document.createElement('option');
144 |
145 | $option.setAttribute('value', options[i]);
146 | $option.innerHTML = options[i];
147 | selectFilter.appendChild($option);
148 | }
149 |
150 | selectFilter.setAttribute('id', 'filter-' + _self.elem);
151 | selectFilter.setAttribute('class', 'fp-select');
152 | selectFilter.addEventListener('change', function() {
153 | _self.setFilter(this.value);
154 | }, false
155 | );
156 | controlBar.appendChild(selectFilter);
157 |
158 | var toFrameLabel = document.createElement('label'),
159 | toFrameInput = document.createElement('input'),
160 | toFrameSubmit = document.createElement('input');
161 |
162 | toFrameLabel.className = "to-frame";
163 |
164 | toFrameInput.type = 'text';
165 | toFrameInput.name = 'frame';
166 | toFrameInput.value = _self.startFrame;
167 | toFrameInput.className = "to-frame";
168 | _self.toFrameInput = toFrameInput;
169 |
170 | toFrameSubmit.type = 'submit';
171 | toFrameSubmit.value = 'go to frame';
172 |
173 | toFrameSubmit.onclick = function() {
174 | value = parseInt(toFrameInput.value, 10)
175 | _self.gotoFrame(value);
176 | }
177 |
178 | toFrameLabel.appendChild(toFrameInput);
179 | toFrameLabel.appendChild(toFrameSubmit);
180 | controlBar.appendChild(toFrameLabel);
181 |
182 | // Add control bar
183 | this.divCont.appendChild(controlBar);
184 | };
185 |
186 | FramePlayer.prototype.play = function() {
187 | this.getFile(this.jsonVideoSrc, function(player) {
188 | if (player.paused) {
189 | player.render(player);
190 | player.drawFrame(player);
191 | }else{
192 | player.render(player);
193 | }
194 | });
195 | };
196 |
197 | FramePlayer.prototype.resume = function() {
198 | var btnPlay = document.getElementById('play-' + this.elem),
199 | btnPause = document.getElementById('pause-' + this.elem);
200 |
201 | btnPlay.style.display = 'none';
202 | btnPause.style.display = 'block';
203 | this.paused = false;
204 | };
205 |
206 | FramePlayer.prototype.pause = function() {
207 | var btnPlay = document.getElementById('play-' + this.elem),
208 | btnPause = document.getElementById('pause-' + this.elem);
209 |
210 | btnPlay.style.display = 'block';
211 | btnPause.style.display = 'none';
212 | this.paused = true;
213 | };
214 |
215 | FramePlayer.prototype.reverse = function() {
216 | var btnBackwards = document.getElementById('backwards-' + this.elem);
217 | this.backwards = !this.backwards;
218 | value = this.backwards ? 'Forward' :'Backward';
219 | btnBackwards.innerHTML = value;
220 | };
221 |
222 | FramePlayer.prototype.gotoFrame = function(value) {
223 |
224 | if (value !== parseInt(value, 10)) return;
225 |
226 | this.currentFrame = this.startFrame = this.toFrameInput.value = value;
227 |
228 | if (this.jsonVideoFile === undefined) {
229 | this.play();
230 | } else {
231 | this.drawFrame(this);
232 | }
233 | };
234 |
235 | FramePlayer.prototype.setFilter = function(filter) {
236 | var canvas = document.querySelector('#' + this.elem + ' canvas');
237 |
238 | switch (filter) {
239 | case 'normal':
240 | canvas.setAttribute('class', '');
241 | break;
242 | case 'grayscale':
243 | canvas.setAttribute('class', 'fp-grayscale');
244 | break;
245 | case 'sepia':
246 | canvas.setAttribute('class', 'fp-sepia');
247 | break;
248 | case 'invert':
249 | canvas.setAttribute('class', 'fp-invert');
250 | break;
251 | default:
252 | break;
253 | }
254 | };
255 |
256 | FramePlayer.prototype.getFile = function(src, callback) {
257 | var _HTTP = new XMLHttpRequest(),
258 | _self = this,
259 | p = document.createElement('p');
260 |
261 | if (_HTTP) {
262 | _HTTP.open('GET', src, true);
263 | _HTTP.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
264 | _HTTP.send(null);
265 |
266 | _HTTP.onprogress = function() {
267 | p.innerHTML = 'Loading...';
268 | p.setAttribute('class', 'fp-loading');
269 | _self.divCont.appendChild(p);
270 | };
271 |
272 | if (typeof(_HTTP.onload) !== undefined) {
273 | _HTTP.onload = function() {
274 | _self.divCont.removeChild(p);
275 | _self.jsonVideoFile = JSON.parse(this.responseText)
276 | callback(_self);
277 | _HTTP = null;
278 | };
279 | } else {
280 | _HTTP.onreadystatechange = function() {
281 | if (_HTTP.readyState === 4) {
282 | _self.divCont.removeChild(p);
283 | _self.jsonVideoFile = JSON.parse(this.responseText)
284 | callback(_self);
285 | _HTTP = null;
286 | }
287 | };
288 | }
289 | } else {
290 | throw('Error loading file.');
291 | }
292 | };
293 |
294 | // Polyfill
295 | FramePlayer.prototype.initializeRequestAnimationFrame = function() {
296 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
297 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
298 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel
299 | // MIT license
300 |
301 | var lastTime = 0;
302 | var vendors = ['ms', 'moz', 'webkit', 'o'];
303 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
304 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
305 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
306 | || window[vendors[x]+'CancelRequestAnimationFrame'];
307 | }
308 |
309 | if (!window.requestAnimationFrame)
310 | window.requestAnimationFrame = function(callback, element) {
311 | var currTime = new Date().getTime();
312 | var timeToCall = Math.max(0, 16 - (currTime - lastTime));
313 | var id = window.setTimeout(function() { callback(currTime + timeToCall); },
314 | timeToCall);
315 | lastTime = currTime + timeToCall;
316 | return id;
317 | };
318 |
319 | if (!window.cancelAnimationFrame)
320 | window.cancelAnimationFrame = function(id) {
321 | clearTimeout(id);
322 | };
323 | };
324 |
--------------------------------------------------------------------------------
/videos_src/dragons.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vagnervjs/frame-player/682164d688d49440e2539f52370b5abde98c364b/videos_src/dragons.mp4
--------------------------------------------------------------------------------