From be75ddb779bb48b62b8c9cf8431276f22c5fe8e9 Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 2 Feb 2016 16:44:05 +0800 Subject: [PATCH 01/55] Add ProcessingController and VideoProcessingService to run ffmpeg and mp4box --- api/controllers/ProcessingController.js | 18 ++++++++++++++++++ api/services/VideoProcessingService.js | 16 ++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 api/controllers/ProcessingController.js create mode 100644 api/services/VideoProcessingService.js diff --git a/api/controllers/ProcessingController.js b/api/controllers/ProcessingController.js new file mode 100644 index 0000000..9cef99a --- /dev/null +++ b/api/controllers/ProcessingController.js @@ -0,0 +1,18 @@ +/** + * ProcessingController + * + * @description :: Server-side logic for managing processings + * @help :: See http://links.sailsjs.org/docs/controllers + */ + +module.exports = { + + /** + * Usage: POST /api/processing/run + * Content: {dir: ':dir'} + **/ + run: function (req, res) { + VideoProcessing.automateProcessing({dir: req.param('dir')}); + } +}; + diff --git a/api/services/VideoProcessingService.js b/api/services/VideoProcessingService.js new file mode 100644 index 0000000..951adf6 --- /dev/null +++ b/api/services/VideoProcessingService.js @@ -0,0 +1,16 @@ +/** + * VideoProcessingService + * + * @description :: Server-side logic to automate video processing + * @help :: See http://links.sailsjs.org/docs/service + */ + + + +module.exports = { + + automateProcessing: function(options) { + // Type your code here + // To get dir, use options.dir + } +}; \ No newline at end of file From 03beba2a511de097330309e71dc6fb15d5e3fb7c Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Tue, 2 Feb 2016 16:46:02 +0800 Subject: [PATCH 02/55] Edited package.json to include new packages - fluent-ffmpeg --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 2dbf788..41add67 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dependencies": { "bower": "^1.7.2", "ejs": "~0.8.4", + "fluent-ffmpeg": "^2.0.1", "grunt": "0.4.2", "grunt-contrib-clean": "~0.5.0", "grunt-contrib-coffee": "~0.10.1", @@ -20,6 +21,7 @@ "grunt-sails-linker": "~0.9.5", "grunt-sync": "~0.0.4", "include-all": "~0.1.3", + "mp4box": "^0.3.8", "rc": "~0.5.0", "sails": "~0.11.0", "sails-disk": "~0.10.0" From d2800c995bf2d220c76a7eb0ff209993234c17de Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Tue, 2 Feb 2016 17:16:16 +0800 Subject: [PATCH 03/55] For Jiale's Testing --- api/controllers/ProcessingController.js | 3 ++- api/services/VideoProcessingService.js | 13 ++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/api/controllers/ProcessingController.js b/api/controllers/ProcessingController.js index 9cef99a..1fc850d 100644 --- a/api/controllers/ProcessingController.js +++ b/api/controllers/ProcessingController.js @@ -12,7 +12,8 @@ module.exports = { * Content: {dir: ':dir'} **/ run: function (req, res) { - VideoProcessing.automateProcessing({dir: req.param('dir')}); + console.log(req.param('dir')); + VideoProcessingService.automateProcessing({dir: req.param('dir')}); } }; diff --git a/api/services/VideoProcessingService.js b/api/services/VideoProcessingService.js index 951adf6..2aba4cd 100644 --- a/api/services/VideoProcessingService.js +++ b/api/services/VideoProcessingService.js @@ -5,12 +5,23 @@ * @help :: See http://links.sailsjs.org/docs/service */ - +var fs = require('fs'); +var ffmpeg = require('fluent-ffmpeg'); module.exports = { automateProcessing: function(options) { // Type your code here // To get dir, use options.dir + dirPath = options.dir; + var videoFile = fs.readdir(dirPath, function(error, listOfVids) { + // If an error has occurred + if (error) { + // Print the error for now, throw an error for a error handler later + console.log(error) + } + }) + + console.log(videoFile[0]); } }; \ No newline at end of file From 289c2c5aabb131263cc046f3ab4cd93078c96cb4 Mon Sep 17 00:00:00 2001 From: jiale Date: Wed, 17 Feb 2016 13:24:10 +0800 Subject: [PATCH 04/55] Add more test cases to VideoController.test.js --- .../controllers/VideoController.test.js | 138 +++++++++++++++--- 1 file changed, 120 insertions(+), 18 deletions(-) diff --git a/test/integration/controllers/VideoController.test.js b/test/integration/controllers/VideoController.test.js index 07b89fa..cd6153e 100644 --- a/test/integration/controllers/VideoController.test.js +++ b/test/integration/controllers/VideoController.test.js @@ -2,13 +2,17 @@ var request = require('supertest'); describe('VideoController', function () { var credentials = { username: 'test', password: 'testtesttest'}; - var agent = request.agent('http://localhost:1337'); + var vid1 = {title: 'Mission Impossible', videoDir: '/video/1', thumbnailDir: '/video/1/a.jpg'}; + var vid2 = {title: 'Mission Possible', videoDir: '/video/1', thumbnailDir: '/video/1/a.jpg'}; + var vid3 = {title: 'Finding Nemo', videoDir: '/video/2', thumbnailDir: '/video/2/a.jpg'}; describe('#create', function () { + var agent = request.agent('http://localhost:1337'); + it('should not create a new Video object before login', function (done) { request(sails.hooks.http.app) .post('/api/video') - .send({ title: 'Mission Impossible', videoDir: '/video/1', thumbnailDir: '/video/1/a.jpg'}) + .send(vid1) .expect(403) .end(function (err, res) { if (err) done(err); @@ -29,7 +33,7 @@ describe('VideoController', function () { agent .post('/api/video') - .send({ title: 'Mission Impossible', videoDir: '/video/1', thumbnailDir: '/video/1/a.jpg'}) + .send(vid1) .set('Accept', 'application/json') .expect(200) .end(function (videoCreateErr, videoCreateRes) { @@ -43,39 +47,137 @@ describe('VideoController', function () { }); describe('#read', function () { + var agent = request.agent('http://localhost:1337'); + + it('should not read Video object based on the id before login', function (done) { + request(sails.hooks.http.app) + .get('/api/video/1') + .expect(403) + .end(function (err, res) { + if (err) done(err); + + (res.text).should.match('You are not permitted to perform this action.'); + done(); + }); + }); + it('should return a Video object based on the id given after login', function (done) { agent - .get('/api/video/1') - .set('Accept', 'application/json') + .post('/api/user/login') + .send(credentials) .expect(200) - .end(function (videoCreateErr, videoCreateRes) { - if (videoCreateErr) done(videoCreateErr); + .end(function (signinErr, signinRes) { + agent + .get('/api/video/1') + .set('Accept', 'application/json') + .expect(200) + .end(function (videoCreateErr, videoCreateRes) { + if (videoCreateErr) done(videoCreateErr); - (videoCreateRes.body.title).should.equal('Mission Impossible'); - done(); + (videoCreateRes.body.title).should.equal('Mission Impossible'); + done(); + }); }); }); }); describe('#readAll', function () { + var agent = request.agent('http://localhost:1337'); + + it('should not read list of all Video object before login', function (done) { + request(sails.hooks.http.app) + .get('/api/video/') + .expect(403) + .end(function (err, res) { + if (err) done(err); + + (res.text).should.match('You are not permitted to perform this action.'); + done(); + }); + }); + it('should return a list of all Video object after login', function (done) { agent - .get('/api/video') - .set('Accept', 'application/json') - .expect(200, done); + .post('/api/user/login') + .send(credentials) + .expect(200) + .end(function (signinErr, signinRes) { + agent + .get('/api/video') + .set('Accept', 'application/json') + .expect(200, done); + }); + }); + }); + + describe('#update', function () { + var agent = request.agent('http://localhost:1337'); + + it('should not read update Video object based on the id given before login', function (done) { + request(sails.hooks.http.app) + .put('/api/video/1') + .send(vid2) + .expect(403) + .end(function (err, res) { + if (err) done(err); + + (res.text).should.match('You are not permitted to perform this action.'); + done(); + }); + }); + + it('should update Video object based on the id given after login', function (done) { + agent + .post('/api/user/login') + .send(credentials) + .expect(200) + .end(function (err, res) { + agent + .put('/api/video/1') + .send(vid2) + .set('Accept', 'application/json') + .expect(200) + .end(function (err, res) { + if (err) done(err); + + res.body[0].should.be.instanceof(Object).and.have.property('title', 'Mission Possible'); + done(); + }); + }); }); }); describe('#destroy', function () { - it('should destroy the Video object based on the id given after login', function (done) { - agent + var agent = request.agent('http://localhost:1337'); + + it('should not read delete Video object based on the id given before login', function (done) { + request(sails.hooks.http.app) .delete('/api/video/1') - .end(function (videoDeleteErr, videoDeleteRes) { - if (videoDeleteErr) done (videoDeleteErr); + .expect(403) + .end(function (err, res) { + if (err) done(err); + (res.text).should.match('You are not permitted to perform this action.'); + done(); + }); + }); + + it('should destroy the Video object based on the id given after login', function (done) { + agent + .post('/api/user/login') + .send(credentials) + .expect(200) + .end(function (err, res) { agent - .get('/api/video/1') - .expect(404, done); + .delete('/api/video/1') + .end(function (videoDeleteErr, videoDeleteRes) { + if (videoDeleteErr) done (videoDeleteErr); + + // Try to find the deleted video + agent + .get('/api/video/1') + .expect(404, done); + }); }); }); }); From 307ad495f5b2688e9591f7da6664315c40e122d2 Mon Sep 17 00:00:00 2001 From: jiale Date: Fri, 19 Feb 2016 22:47:34 +0800 Subject: [PATCH 05/55] Add more test cases for VideoController --- test/fixtures/video.json | 7 ++++++ .../controllers/VideoController.test.js | 25 ++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/video.json diff --git a/test/fixtures/video.json b/test/fixtures/video.json new file mode 100644 index 0000000..bdd4d1a --- /dev/null +++ b/test/fixtures/video.json @@ -0,0 +1,7 @@ +[ + { + "title": "Finding Nemo", + "videoDir": "/video/2", + "thumbnailDir": "/video/2/a.jpg" + } +] \ No newline at end of file diff --git a/test/integration/controllers/VideoController.test.js b/test/integration/controllers/VideoController.test.js index cd6153e..28059c0 100644 --- a/test/integration/controllers/VideoController.test.js +++ b/test/integration/controllers/VideoController.test.js @@ -4,7 +4,6 @@ describe('VideoController', function () { var credentials = { username: 'test', password: 'testtesttest'}; var vid1 = {title: 'Mission Impossible', videoDir: '/video/1', thumbnailDir: '/video/1/a.jpg'}; var vid2 = {title: 'Mission Possible', videoDir: '/video/1', thumbnailDir: '/video/1/a.jpg'}; - var vid3 = {title: 'Finding Nemo', videoDir: '/video/2', thumbnailDir: '/video/2/a.jpg'}; describe('#create', function () { var agent = request.agent('http://localhost:1337'); @@ -51,7 +50,7 @@ describe('VideoController', function () { it('should not read Video object based on the id before login', function (done) { request(sails.hooks.http.app) - .get('/api/video/1') + .get('/api/video/2') .expect(403) .end(function (err, res) { if (err) done(err); @@ -68,7 +67,7 @@ describe('VideoController', function () { .expect(200) .end(function (signinErr, signinRes) { agent - .get('/api/video/1') + .get('/api/video/2') .set('Accept', 'application/json') .expect(200) .end(function (videoCreateErr, videoCreateRes) { @@ -105,7 +104,15 @@ describe('VideoController', function () { agent .get('/api/video') .set('Accept', 'application/json') - .expect(200, done); + .expect(200) + .end(function (err, res) { + if (err) done(err); + + res.body.should.be.instanceof(Array).and.have.length(2); + res.body[0].should.be.instanceof(Object).and.have.property('title', 'Finding Nemo'); + res.body[1].should.be.instanceof(Object).and.have.property('title', 'Mission Impossible'); + done(); + }); }); }); }); @@ -115,7 +122,7 @@ describe('VideoController', function () { it('should not read update Video object based on the id given before login', function (done) { request(sails.hooks.http.app) - .put('/api/video/1') + .put('/api/video/2') .send(vid2) .expect(403) .end(function (err, res) { @@ -133,7 +140,7 @@ describe('VideoController', function () { .expect(200) .end(function (err, res) { agent - .put('/api/video/1') + .put('/api/video/2') .send(vid2) .set('Accept', 'application/json') .expect(200) @@ -152,7 +159,7 @@ describe('VideoController', function () { it('should not read delete Video object based on the id given before login', function (done) { request(sails.hooks.http.app) - .delete('/api/video/1') + .delete('/api/video/2') .expect(403) .end(function (err, res) { if (err) done(err); @@ -169,13 +176,13 @@ describe('VideoController', function () { .expect(200) .end(function (err, res) { agent - .delete('/api/video/1') + .delete('/api/video/2') .end(function (videoDeleteErr, videoDeleteRes) { if (videoDeleteErr) done (videoDeleteErr); // Try to find the deleted video agent - .get('/api/video/1') + .get('/api/video/2') .expect(404, done); }); }); From cf8c54e1402aa2613ff879f2bae3f1e46fdfe156 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 23 Feb 2016 10:46:20 +0800 Subject: [PATCH 06/55] Fixed login form input box extending out of parent width --- assets/styles/custom/login.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assets/styles/custom/login.less b/assets/styles/custom/login.less index 4794eb2..93ac9e1 100644 --- a/assets/styles/custom/login.less +++ b/assets/styles/custom/login.less @@ -53,6 +53,9 @@ outline: none; padding: 0px 20px; width: 100%; + -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */ + -moz-box-sizing: border-box; /* Firefox, other Gecko */ + box-sizing: border-box; /* Opera/IE 8+ */ } input { From 7785f968759755ee553308d6825c0492a34e3da3 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 23 Feb 2016 13:23:30 +0800 Subject: [PATCH 07/55] Add feedback to user to login on edit --- assets/js/public/controllers/editController.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/assets/js/public/controllers/editController.js b/assets/js/public/controllers/editController.js index 8a1c532..5690c57 100644 --- a/assets/js/public/controllers/editController.js +++ b/assets/js/public/controllers/editController.js @@ -41,8 +41,24 @@ angular.module('zoomableApp').controller('editController', function($scope, $mdT // set form back to clean state to disable save button $scope.videoForm.$setPristine(); }) - .error(function(data) { + .error(function(data, status) { console.log('Error: ' + data); + if (status == 403) { + // user is forbidden to perform action. Ask user to sign in again using toast. + var toast = $mdToast.simple() + .content('Please login to make changes.') + .action('Login').highlightAction(true) + .hideDelay(false) + .position('top right') + .parent(document.getElementById('toast-area')); + + $mdToast.show(toast).then(function(response) { + if (response == 'ok') { + // redirect to dashboard page + window.location = '/'; + } + }); + } }); }; From 605ae712a4335ee96b17a25981413df56410d43a Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 23 Feb 2016 13:54:30 +0800 Subject: [PATCH 08/55] Update correct routing to login page if user is not logged in --- api/controllers/PageController.js | 28 +++++++++++++++++++--------- views/layout.ejs | 4 ++-- views/navbar.ejs | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/api/controllers/PageController.js b/api/controllers/PageController.js index aeec543..a066cd2 100644 --- a/api/controllers/PageController.js +++ b/api/controllers/PageController.js @@ -12,9 +12,8 @@ module.exports = { // If not logged in, show the public view. if (!req.session.me) { - //return res.view('homepage'); return res.view('homepage', { - me: [], + user: [], video: [] }); } @@ -28,15 +27,19 @@ module.exports = { if (!user) { sails.log.verbose('Session refers to a user who no longer exists- did you delete a user, then try to refresh the page with an open tab logged-in as that user?'); - //return res.view('homepage'); return res.view('homepage', { - me: [], + user: [], video: [] }); } + // returned filtered user object + var userObj = { + username: user.username, + email: user.email + }; return res.view('dashboard', { - me: user, + user: userObj, video: [] }); @@ -47,7 +50,10 @@ module.exports = { // If not logged in, show the public view. if (!req.session.me) { - return res.view('homepage'); + return res.view('homepage', { + user: [], + video: [] + }); } // Otherwise, look up the logged-in user and show the logged-in view, @@ -60,7 +66,7 @@ module.exports = { if (!user) { sails.log.verbose('Session refers to a user who no longer exists- did you delete a user, then try to refresh the page with an open tab logged-in as that user?'); return res.view('homepage', { - me: [], + user: [], video: [] }); } @@ -71,9 +77,13 @@ module.exports = { // return error } - // if successful, return user and video object to frontend + // if successful, return filtered user and video object to frontend + var userObj = { + username: user.username, + email: user.email + }; return res.view('edit', { - me: user, + user: userObj, video: video }); diff --git a/views/layout.ejs b/views/layout.ejs index 025ec81..70d07b5 100644 --- a/views/layout.ejs +++ b/views/layout.ejs @@ -38,14 +38,14 @@ - <% if (me.length != 0) { %> + <% if (user.length != 0) { %> <%- partial('navbar.ejs') %> <% } %> diff --git a/views/navbar.ejs b/views/navbar.ejs index 5dbec21..52f383c 100644 --- a/views/navbar.ejs +++ b/views/navbar.ejs @@ -24,7 +24,7 @@ - <%= me.username %> + <%= user.username %> From 34c4ea30d65bc9493b9bc349ba7e9b1efd979b35 Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 23 Feb 2016 14:58:12 +0800 Subject: [PATCH 09/55] Add uploadVideo API to VideoController --- api/controllers/VideoController.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js index e67e2b5..b8e0413 100644 --- a/api/controllers/VideoController.js +++ b/api/controllers/VideoController.js @@ -92,6 +92,30 @@ module.exports = { if (err) throw err; res.json(video.videoDir); }); + }, + + /** + * `VideoController.uploadVideo()` + * Usage: POST /api/video/uploadVideo + * Content: {id: ':id', video: 'attach video file here'} + **/ + uploadVideo: function (req, res) { + req.file('video').upload({ + dirname: sails.config.appPath + '/vid/' + req.param('id') + }, function (err, uploadedFiles) { + if (err) return res.negotiate(err); + + // If no files were uploaded, respond with an error. + if (uploadedFiles.length === 0){ + return res.badRequest('No file was uploaded'); + } + + return res.json({ + message: uploadedFiles.length + ' file(s) uploaded successfully!', + files: uploadedFiles, + textParams: req.params.all() + }); + }); } }; From ca5e5a042e1c68d94086df0b463083f8d32a8d17 Mon Sep 17 00:00:00 2001 From: linxea Date: Tue, 23 Feb 2016 15:02:27 +0800 Subject: [PATCH 10/55] Move player to dashboard --- api/controllers/PageController.js | 8 +- assets/images/ic_caption.svg | 12 + assets/images/ic_fullscreen.svg | 8 + assets/images/ic_fullscreen_exit.svg | 8 + assets/images/ic_pause.svg | 7 + assets/images/ic_play.svg | 7 + assets/images/ic_replay.svg | 10 + assets/images/ic_setting.svg | 16 + assets/images/ic_snapshot.svg | 13 + assets/images/ic_volume_high.svg | 10 + assets/images/ic_volume_low.svg | 8 + assets/images/ic_volume_off.svg | 9 + assets/images/ic_zoom_in.svg | 11 + assets/images/ic_zoom_out.svg | 10 + assets/js/public/player/initShakaPlayer.js | 26 ++ assets/js/public/player/player.js | 497 +++++++++++++++++++++ assets/styles/importer.less | 2 + assets/styles/player/canvas.css.less | 261 +++++++++++ config/routes.js | 1 + tasks/config/copy.js | 2 +- tasks/pipeline.js | 2 + views/layout.ejs | 4 + views/player.ejs | 148 ++++++ 23 files changed, 1078 insertions(+), 2 deletions(-) create mode 100644 assets/images/ic_caption.svg create mode 100644 assets/images/ic_fullscreen.svg create mode 100644 assets/images/ic_fullscreen_exit.svg create mode 100644 assets/images/ic_pause.svg create mode 100644 assets/images/ic_play.svg create mode 100644 assets/images/ic_replay.svg create mode 100644 assets/images/ic_setting.svg create mode 100644 assets/images/ic_snapshot.svg create mode 100644 assets/images/ic_volume_high.svg create mode 100644 assets/images/ic_volume_low.svg create mode 100644 assets/images/ic_volume_off.svg create mode 100644 assets/images/ic_zoom_in.svg create mode 100644 assets/images/ic_zoom_out.svg create mode 100644 assets/js/public/player/initShakaPlayer.js create mode 100644 assets/js/public/player/player.js create mode 100644 assets/styles/player/canvas.css.less create mode 100644 views/player.ejs diff --git a/api/controllers/PageController.js b/api/controllers/PageController.js index a066cd2..1d93f18 100644 --- a/api/controllers/PageController.js +++ b/api/controllers/PageController.js @@ -89,6 +89,12 @@ module.exports = { }); }); - } + }, + showPlayerPage: function (req, res) { + return res.view('player', { + user: [], + video: [] + }); + } }; diff --git a/assets/images/ic_caption.svg b/assets/images/ic_caption.svg new file mode 100644 index 0000000..a5a3fc4 --- /dev/null +++ b/assets/images/ic_caption.svg @@ -0,0 +1,12 @@ + + + + + + diff --git a/assets/images/ic_fullscreen.svg b/assets/images/ic_fullscreen.svg new file mode 100644 index 0000000..281f9a6 --- /dev/null +++ b/assets/images/ic_fullscreen.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/assets/images/ic_fullscreen_exit.svg b/assets/images/ic_fullscreen_exit.svg new file mode 100644 index 0000000..8470879 --- /dev/null +++ b/assets/images/ic_fullscreen_exit.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/assets/images/ic_pause.svg b/assets/images/ic_pause.svg new file mode 100644 index 0000000..9f6a98b --- /dev/null +++ b/assets/images/ic_pause.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/assets/images/ic_play.svg b/assets/images/ic_play.svg new file mode 100644 index 0000000..418ff60 --- /dev/null +++ b/assets/images/ic_play.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/assets/images/ic_replay.svg b/assets/images/ic_replay.svg new file mode 100644 index 0000000..e9d9690 --- /dev/null +++ b/assets/images/ic_replay.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/assets/images/ic_setting.svg b/assets/images/ic_setting.svg new file mode 100644 index 0000000..57ba3ad --- /dev/null +++ b/assets/images/ic_setting.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/assets/images/ic_snapshot.svg b/assets/images/ic_snapshot.svg new file mode 100644 index 0000000..3eba8ef --- /dev/null +++ b/assets/images/ic_snapshot.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/assets/images/ic_volume_high.svg b/assets/images/ic_volume_high.svg new file mode 100644 index 0000000..857bf70 --- /dev/null +++ b/assets/images/ic_volume_high.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/assets/images/ic_volume_low.svg b/assets/images/ic_volume_low.svg new file mode 100644 index 0000000..2225281 --- /dev/null +++ b/assets/images/ic_volume_low.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/assets/images/ic_volume_off.svg b/assets/images/ic_volume_off.svg new file mode 100644 index 0000000..58ed7f6 --- /dev/null +++ b/assets/images/ic_volume_off.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/assets/images/ic_zoom_in.svg b/assets/images/ic_zoom_in.svg new file mode 100644 index 0000000..e0f8a70 --- /dev/null +++ b/assets/images/ic_zoom_in.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/assets/images/ic_zoom_out.svg b/assets/images/ic_zoom_out.svg new file mode 100644 index 0000000..8bc0b21 --- /dev/null +++ b/assets/images/ic_zoom_out.svg @@ -0,0 +1,10 @@ + + + + + + diff --git a/assets/js/public/player/initShakaPlayer.js b/assets/js/public/player/initShakaPlayer.js new file mode 100644 index 0000000..b586ccc --- /dev/null +++ b/assets/js/public/player/initShakaPlayer.js @@ -0,0 +1,26 @@ +function initShakaPlayer() { + // Install polyfills. + shaka.polyfill.installAll(); + + // Find the video element. + var video = document.getElementById('video'); + + // Construct a Player to wrap around it. + var player = new shaka.player.Player(video); + + // Attach the player to the window so that it can be easily debugged. + window.player = player; + + // Listen for errors from the Player. + player.addEventListener('error', function(event) { + console.error(event); + }); + + // Construct a DashVideoSource to represent the DASH manifest. + var mpdUrl = $('#mpdSource').val(); + var estimator = new shaka.util.EWMABandwidthEstimator(); + var source = new shaka.player.DashVideoSource(mpdUrl, null, estimator); + + // Load the source into the Player. + player.load(source); +} \ No newline at end of file diff --git a/assets/js/public/player/player.js b/assets/js/public/player/player.js new file mode 100644 index 0000000..93f9586 --- /dev/null +++ b/assets/js/public/player/player.js @@ -0,0 +1,497 @@ + +var Player = function(vid,canv) { + + this.video = vid; + this.canvas = canv; + this.ctx = canv.getContext('2d'); + this.scaleFactor = 1.1; + this.zoomFactor = 1; + this.dimensions = { cw:canvas.width, ch:canvas.height }; + this.last; + this.dragStart; + this.dragged; + this.mouseactions; + this.scroll; + this.controls; + this.volume; + this.seek; + this.zoom; + this.transforms; + this.util; + + this.init = function() { + this.scroll = new Scroll(this); + this.volume = new Volume(this); + this.zoom = new Zoom(this); + this.controls = new Controls(this); + this.transforms = new Transforms(this); + this.seek = new Seek(this); + this.transforms = new Transforms(this); + this.util = new Util(this); + this.transforms.redraw(); + this.last = { x: canvas.width/2, y: canvas.height/2 }; + this.volume.setVolume(0.5); //set default vol of video + this.mouseactions = new MouseActions(this); + }; + + var MouseActions = function(player) { + player.canvas.addEventListener('mousedown',function(event) { player.mouseactions.mouseDown(event); },false); + player.canvas.addEventListener('mousemove',function(event) { player.mouseactions.mouseMove(event); },false); + player.canvas.addEventListener('mouseup',function(event) { player.mouseactions.mouseUp(event); },false); + + + this.mouseDown = function(evt){ + document.body.style.mozUserSelect = + document.body.style.webkitUserSelect = + document.body.style.userSelect = 'none'; + player.last.x = evt.offsetX || (evt.pageX - player.canvas.offsetLeft); + player.last.y = evt.offsetY || (evt.pageY - player.canvas.offsetTop); + player.dragStart = player.ctx.transformedPoint(player.last.x,player.last.y); + player.dragged = false; + }; + this.mouseMove = function(evt){ + player.last.x = evt.offsetX || (evt.pageX - player.canvas.offsetLeft); + player.last.y = evt.offsetY || (evt.pageY - player.canvas.offsetTop); + player.dragged = true; + if (player.dragStart){ + player.transforms.outerTranslate(); + } + }; + this.mouseUp = function(evt){ + player.dragStart = null; + } + }; + + var Scroll = function(player) { + canvas.addEventListener('DOMMouseScroll',function(event) { player.scroll.handle(event); },false); + canvas.addEventListener('mousewheel',function(event) { player.scroll.handle(event); },false); + + this.handle = function(evt){ + var delta = evt.wheelDelta ? evt.wheelDelta/40 : evt.detail ? -evt.detail : 0; + if (delta) { + //updateSliderUI(zoomCtrl); + player.zoom.zoom(delta, player.last.x, player.last.y); + player.controls.updateZoomUI(); + } + return evt.preventDefault() && false; + } + }; + + var Controls = function(player) { + + /* binds to UI and adds listeners for controls*/ + this.playPauseBtn = document.getElementById('playPauseBtn'); + this.uiControls = document.getElementById('uiControls'); + this.currentTimeTxt = document.getElementById('currentTimeTxt'); + this.totalTimeTxt = document.getElementById('totalTimeTxt'); + this.seekCtrl = document.getElementById('seekCtrl'); + this.volumeBtn = document.getElementById('volumeBtn'); + this.volumeCtrl = document.getElementById('volumeCtrl'); + this.zoomOutBtn = document.getElementById('zoomOutBtn'); + this.zoomCtrl = document.getElementById('zoomCtrl'); + this.zoomInBtn = document.getElementById('zoomInBtn'); + + player.video.addEventListener('loadedmetadata',function(){ + player.controls.getVideoLength() + },false); + this.playPauseBtn.addEventListener('click',function(){ + player.controls.playPauseVideo(this.playPauseVideo); + },false); + player.video.addEventListener('pause',function(){ + player.controls.changeToPauseState(this.playPauseBtn, this.uiControls); + },false); + player.video.addEventListener('play',function(){ + player.controls.changeToPlayState(this.playPauseBtn, this.uiControls); + },false); + this.volumeBtn.addEventListener('click',function(){ + player.volume.toggleMuteState(event); + player.controls.updateSliderUI(player.controls.volumeCtrl); + },false); + this.volumeCtrl.addEventListener('change',function(){ + player.volume.volumeAdjust(); + player.controls.updateSliderUI(player.controls.volumeCtrl); + },false); + player.video.addEventListener('volumechange',function(){ + player.controls.updateSliderUI(player.controls.volumeCtrl); + },false); + this.volumeCtrl.addEventListener('mousemove',function(){ + player.controls.updateSliderUI(player.controls.volumeCtrl); + },false); + this.zoomInBtn.addEventListener('click',function(){ + player.zoom.in(); + },false); + this.zoomOutBtn.addEventListener('click',function(){ + player.zoom.out(); + },false); + this.zoomCtrl.addEventListener('change',function(){ + player.zoom.adjust(); + },false); + this.zoomCtrl.addEventListener('mousemove',function(){ + player.controls.updateSliderUI(player.controls.zoomCtrl); + },false); + + /* Play or pause the video */ + this.playPauseVideo = function() { + if(player.video.paused) { + player.video.play(); + } + else { + player.video.pause(); + } + } + + /* Updates icon to "play" button during pause state, show UI controls bar */ + this.changeToPauseState = function() { + this.playPauseBtn.className = 'play'; + this.uiControls.className = ''; + } + + /* Updates icon to "pause" button during play state, hide UI controls bar */ + this.changeToPlayState = function() { + this.playPauseBtn.className = 'pause'; + this.uiControls.className = 'hideOnHover'; + } + /* Retrieve total duration of video and update total time text */ + this.getVideoLength = function() { + var convertedTotalTime = player.util.convertSecondsToHMS(player.video.duration); + this.totalTimeTxt.innerHTML = convertedTotalTime; + }; + + /* Convert and update current time text */ + this.updateCurrentTimeText = function(time) { + var convertedTime = player.util.convertSecondsToHMS(time); + this.currentTimeTxt.innerHTML = convertedTime; + }; + + /* Update zoom control UI */ + this.updateZoomUI = function() { + this.zoomCtrl.value = player.util.convertScaleToPercent(player.transforms.xform.a); + this.updateSliderUI(this.zoomCtrl); + }; + + /* Update slider color when slider value changes - for zoomCtrl/volumeCtrl */ + this.updateSliderUI = function(element) { + var gradient = ['to right']; + gradient.push('#ccc ' + (element.value * 100) + '%'); + gradient.push('rgba(255, 255, 255, 0.3) ' + (element.value * 100) + '%'); + gradient.push('rgba(255, 255, 255, 0.3) 100%'); + element.style.background = 'linear-gradient(' + gradient.join(',') + ')'; + }; + + }; + + var Volume = function(player){ + this.previousVolume = { + state: 'low', + value: player.video.volume + }; + this.setVolume = function(val) { + player.video.volume = val; + }; + this.volumeAdjust = function() { + player.video.volume = player.controls.volumeCtrl.value; + if (player.video.volume > 0) { + player.video.muted = false; + if (player.video.volume > 0.5) player.controls.volumeBtn.className = 'high'; + else player.controls.volumeBtn.className = 'low'; + } else { + player.video.muted = true; + player.controls.volumeBtn.className = 'off'; + } + // update previous state at the end so mute can be toggled correctly + player.volume.previousVolume.value = player.video.volume; + player.volume.previousVolume.state = player.volumeBtn.className; + }; + + this.toggleMuteState = function(evt) { + // temporary variables to store current volume values + var currentVolumeState = evt.target.className; + var currentVolumeControlValue = player.video.volume; + + if (currentVolumeState == 'low' || currentVolumeState == 'high') { + evt.target.className = 'off'; + player.video.muted = true; + player.controls.volumeCtrl.value = 0; + player.video.volume = 0; + } + else { + evt.target.className = this.previousVolume.state; + player.video.muted = false; + player.controls.volumeCtrl.value = this.previousVolume.value; + player.video.volume = this.previousVolume.value; + } + + // update previous state + this.previousVolume.state = currentVolumeState; + this.previousVolume.value = currentVolumeControlValue; + } + }; + + var Seek = function(player){ + /* Update seek control value and current time text */ + player.video.addEventListener('timeupdate',function() { player.seek.updateSeekTime(); },false); + player.controls.seekCtrl.addEventListener('change',function() { player.seek.setVideoTime(); },false); + + this.updateSeekTime = function(){ + var newTime = player.video.currentTime/player.video.duration; + var gradient = ['to right']; + var buffered = player.video.buffered; + player.controls.seekCtrl.value = newTime; + if (buffered.length == 0) { + gradient.push('rgba(255, 255, 255, 0.1) 0%'); + } else { + // NOTE: the fallback to zero eliminates NaN. + var bufferStartFraction = (buffered.start(0) / player.video.duration) || 0; + var bufferEndFraction = (buffered.end(0) / player.video.duration) || 0; + var playheadFraction = (player.video.currentTime / player.video.duration) || 0; + gradient.push('rgba(255, 255, 255, 0.1) ' + (bufferStartFraction * 100) + '%'); + gradient.push('rgba(255, 255, 255, 0.7) ' + (bufferStartFraction * 100) + '%'); + gradient.push('rgba(255, 255, 255, 0.7) ' + (playheadFraction * 100) + '%'); + gradient.push('rgba(255, 255, 255, 0.4) ' + (playheadFraction * 100) + '%'); + gradient.push('rgba(255, 255, 255, 0.4) ' + (bufferEndFraction * 100) + '%'); + gradient.push('rgba(255, 255, 255, 0.1) ' + (bufferEndFraction * 100) + '%'); + } + player.controls.seekCtrl.style.background = 'linear-gradient(' + gradient.join(',') + ')'; + + player.controls.updateCurrentTimeText(player.video.currentTime); + }; + /* Change current video time and text according to seek control value */ + this.setVideoTime = function(){ + var seekTo = player.video.duration * player.controls.seekCtrl.value; + player.video.currentTime = seekTo; + player.controls.updateCurrentTimeText(player.video.currentTime); + }; + + }; + + var Zoom = function(player) { + this.maxZoom = 7; + + /* Zooms into the position x, y with the amount clicks */ + this.zoom = function(clicks, x, y){ + //tt(ctx); + var pt = player.ctx.transformedPoint(x, y); + var factor = Math.pow(player.scaleFactor,clicks); + var tx = player.transforms.xform.e; + var ty = player.transforms.xform.f; + var s = player.transforms.xform.a; + if (factor*s >= 1 && factor*s <= this.maxZoom) { + player.transforms.translate(pt.x,pt.y); + player.transforms.scale(factor,factor); + player.transforms.translate(-pt.x,-pt.y); + player.controls.zoomCtrl.value = player.util.convertScaleToPercent(player.transforms.xform.a); + player.transforms.refit(); + } + player.transforms.redraw(); + } + + /* Private function to call zoom(clicks,x,y) from the UI Controls. */ + function zoomHelper(value) { + var tx = player.transforms.xform.e; + var ty = player.transforms.xform.f; + var old_s = player.transforms.xform.a; + var x = player.dimensions.cw/2; + var y = player.dimensions.ch/2; + player.zoom.zoom(value, x, y); + player.controls.updateZoomUI(); + } + /* Adjust zoom by adjusting the slider */ + this.adjust = function() { + var zoomPercent = player.controls.zoomCtrl.value; + var new_s = player.util.convertPercentToScale(zoomPercent); + var old_s = player.transforms.xform.a; + var delta_clicks = Math.log(new_s/old_s) / Math.log(scaleFactor); + zoomHelper(delta_clicks); + } + + /* Adjust zoom by clicking zoom in and out buttons */ + this.in = function() { + zoomHelper(1); + } + this.out = function() { + zoomHelper(-1); + } + } + + var Transforms = function(player) { + player.video.addEventListener('play', function(){ + player.transforms.draw(); + },false); + + var svg = document.createElementNS("http://www.w3.org/2000/svg",'svg'); + this.savedTransforms = []; + this.xform = svg.createSVGMatrix(); + + var save = player.ctx.save; + player.ctx.save = function(){ + this.savedTransforms.push(this.xform.translate(0,0)); + return save.call(player.ctx); + }; + + this.restore = function(){ + var restore = player.ctx.restore; + this.xform = savedTransforms.pop(); + return restore.call(player.ctx); + }; + + this.scale = function(sx,sy){ + var scale = player.ctx.scale; + this.xform = this.xform.scaleNonUniform(sx,sy); + return scale.call(player.ctx,sx,sy); + }; + + this.rotate = function(radians){ + var rotate = player.ctx.rotate; + this.xform = this.xform.rotate(radians*180/Math.PI); + return rotate.call(player.ctx,radians); + }; + + this.translate = function(dx,dy){ + var translate = player.ctx.translate; + this.xform = this.xform.translate(dx,dy); + return translate.call(player.ctx,dx,dy); + }; + + this.transform = function(a,b,c,d,e,f){ + var transform = player.ctx.transform; + var m2 = svg.createSVGMatrix(); + m2.a=a; m2.b=b; m2.c=c; m2.d=d; m2.e=e; m2.f=f; + this.xform = this.xform.multiply(m2); + return transform.call(player.ctx,a,b,c,d,e,f); + }; + + this.setTransform = function(a,b,c,d,e,f){ + var setTransform = player.ctx.setTransform; + this.xform.a = a; + this.xform.b = b; + this.xform.c = c; + this.xform.d = d; + this.xform.e = e; + this.xform.f = f; + return setTransform.call(player.ctx,a,b,c,d,e,f); + }; + + var pt = svg.createSVGPoint(); + player.ctx.transformedPoint = function(x,y){ + pt.x=x; pt.y=y; + return pt.matrixTransform(player.transforms.xform.inverse()); + } + + /* Checks if the viewport borders intersect with the canvas borders + ** If it intersects, then scale/translate back the canvas accordingly to fit the viewport.*/ + this.refit = function() { + var tx = player.transforms.xform.e; + var ty = player.transforms.xform.f; + var s = player.transforms.xform.a; + if (s < 1 || s > player.zoom.maxZoom) { + this.scale(1/s, 1/s); + } + if (tx > 0 ) { + this.translate(-tx/s,0); + } + if (ty > 0) { + this.translate(0,-ty/s); + } + if (tx+player.dimensions.cw*s < player.dimensions.cw) { + var dx = (player.dimensions.cw - tx-player.dimensions.cw*s)/s; + this.translate(dx, 0); + } + if (ty+player.dimensions.ch*s < player.dimensions.ch) { + var dy = (player.dimensions.ch - ty-player.dimensions.ch*s)/s; + this.translate(0, dy); + } + } + + this.draw = function() { + //if(v.paused || v.ended) return false; + player.ctx.drawImage(player.video,0,0,player.dimensions.cw,player.dimensions.ch); + setTimeout(player.transforms.draw,20); + } + + this.redraw = function(){ + // Clear the entire canvas + var p1 = player.ctx.transformedPoint(0,0); + var p2 = player.ctx.transformedPoint(player.dimensions.cw,player.dimensions.ch); + //ctx.clearRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y); + player.ctx.fillStyle = 'rgb(0,0,0)'; + player.ctx.fillRect(p1.x,p1.y,p2.x-p1.x,p2.y-p1.y); + // Alternatively: + // ctx.save(); + // ctx.setTransform(1,0,0,1,0,0); + // ctx.clearRect(0,0,canvas.width,canvas.height); + // ctx.restore(); + player.transforms.refit(); + this.draw(); + } + + this.outerTranslate = function() { + var pt = player.ctx.transformedPoint(player.last.x,player.last.y); + var dx = pt.x-player.dragStart.x; + var dy = pt.y-player.dragStart.y; + var tx = player.transforms.xform.e; + var ty = player.transforms.xform.f; + var flag = 0; + var s = player.transforms.xform.a; + + if (tx+dx <= 0 && tx+player.dimensions.cw*s+dx > player.dimensions.cw) { + this.translate(dx,0); + flag = 1; + } + if (ty+dy <= 0 && ty+player.dimensions.ch*s+dy > player.dimensions.ch) { + this.translate(0,dy); + flag = 1; + } + /* if (flag = 0) { + ctx.translate(pt.x-dragStart.x,pt.y-dragStart.y); + }*/ + this.redraw(); + } + } + + var Util = function(player) { + /* Helper methods to convert between the slider values and transformation matrix values */ + this.convertPercentToScale = function(percent) { + var range = player.zoom.maxZoom - 1; + return percent*range + 1; + } + this.convertScaleToPercent = function(scale) { + var range = player.zoom.maxZoom - 1; + return (scale-1)/range; + } + /* Function to converts seconds to HH:MM:SS format */ + this.convertSecondsToHMS = function(timeInSeconds) { + var formattedTime = ''; + var hours = Math.floor(timeInSeconds / 3600); + var mins = Math.floor((timeInSeconds / 60) % 60); + var secs = Math.floor(timeInSeconds % 60); + + if (secs < 10) + secs = '0' + secs; + if (mins < 10) + mins = '0' + mins; + + formattedTime = hours+':'+mins+':'+secs; + + return formattedTime; + } + } +} + +document.addEventListener('DOMContentLoaded', function() { + var zoomable = new Player(document.getElementById('video'), document.getElementById('canvas')); + zoomable.init(); +}, false); + +/* +var vidCount = 1; +document.addEventListener('DOMContentLoaded', function() { + // To loop through the rows while we are on a column + for(var rowNum = 0; rowNum < 3; rowNum++) { + // To loop through the columns while we are on a row + for(var colNum = 0; colNum < 4; colNum++) { + var zoomable = new Player(document.getElementById('video_' + vidCount), document.getElementById('canvas'), colNum*160, rowNum*120); + zoomable.init() + vidCount++; + } + } +}, false);*/ \ No newline at end of file diff --git a/assets/styles/importer.less b/assets/styles/importer.less index 0a30f78..d2f12e8 100644 --- a/assets/styles/importer.less +++ b/assets/styles/importer.less @@ -34,3 +34,5 @@ @import 'custom/dashboard.less'; @import 'custom/edit.less'; @import 'custom/login.less'; + +@import 'player/canvas.css.less'; diff --git a/assets/styles/player/canvas.css.less b/assets/styles/player/canvas.css.less new file mode 100644 index 0000000..afa6f6d --- /dev/null +++ b/assets/styles/player/canvas.css.less @@ -0,0 +1,261 @@ +#canvasPlayerArea { + position: relative; + width: 640px; + height: 360px; +} + +#uiControls { + width: inherit; +} +#uiControls.hideOnHover { + opacity: 0; + transition: opacity 0.4s; +} +#canvasPlayerArea:hover #uiControls.hideOnHover { + opacity: 1; +} + +#canvas { + width: inherit; + height: inherit; + position: relative; + z-index: 0; +} + +#zoomBarControls { + position: absolute; + /*top: 50%; + left: 86%;*/ + top: 75%; + right: -100px; + transform-origin: 0% 0%; + z-index: 10; + background-color: rgba(0, 0, 0, 0.5); + width: 135px; + height: 24px; + transform: rotate(270deg); + -webkit-transform: rotate(270deg); /* safari */ + -moz-transform: rotate(270deg); /* firefox */ + display: flex; + justify-content: space-between; +} + +#zoomBarControls.fs-adjust { + top: 65%; + right: -90px; +} + +#zoomBarControls button { + background-size: 24px 24px; + border: none; + outline: none; + width: 24px; + height: 24px; + opacity: 0.85; + transform: rotate(180deg); + -webkit-transform: rotate(180deg); /* safari */ + -moz-transform: rotate(180deg); /* firefox */ +} +#zoomBarControls button:hover { + opacity: 1; +} + +#seekBarControls { + position: absolute; + display: flex; + bottom: 36px; + left: 0px; + z-index: 10; + width: inherit; + height: 12px; + background-color: rgba(0, 0, 0, 0.5); + justify-content: space-around; + align-items: flex-end; +} + +#bottomBarControls { + position: absolute; + display: flex; + bottom: 0; + left: 0; + width: inherit; + height: 36px; + background-color: rgba(0, 0, 0, 0.5); + justify-content: space-between; +} + +.flexChild { + display: flex; + padding: 0px 15px; +} + +#bottomBarControls button { + background-size: 32px 32px; + border: none; + outline: none; + margin-top: 2px; + width: 32px; + height: 32px; + opacity: 0.85; +} +#bottomBarControls button:hover { + opacity: 1; +} + +#volumeArea { + display: flex; + padding-right: 5px; +} + +#zoomInBtn { + background: url(../images/ic_zoom_in.svg) no-repeat; +} +#zoomOutBtn { + background: url(../images/ic_zoom_out.svg) no-repeat; +} +#playPauseBtn.play { + background: url(../images/ic_play.svg) no-repeat; +} +#playPauseBtn.pause { + background: url(../images/ic_pause.svg) no-repeat; +} +#playPauseBtn.replay { + background: url(../images/ic_replay.svg) no-repeat; +} +#volumeBtn.low{ + background: url(../images/ic_volume_low.svg) no-repeat; +} +#volumeBtn.high{ + background: url(../images/ic_volume_high.svg) no-repeat; +} +#volumeBtn.off{ + background: url(../images/ic_volume_off.svg) no-repeat; +} +#snapshotBtn { + background: url(../images/ic_snapshot.svg) no-repeat; +} +#settingBtn { + background: url(../images/ic_setting.svg) no-repeat; +} +#captionBtn { + background: url(../images/ic_caption.svg) no-repeat; +} +#fullscreenBtn { + background: url(../images/ic_fullscreen.svg) no-repeat; +} +#fullscreenBtn.exit { + background: url(../images/ic_fullscreen_exit.svg) no-repeat; +} + +#timeTxt { + padding: 12px 8px 0px 5px; + font-size: 11px; + font-family: 'Roboto', sans-serif; + color: white; +} + +/* RANGE SLIDER STYLES */ + +/* removes mozilla default styling */ +#zoomCtrl::-moz-range-track, #seekCtrl::-moz-range-track, #volumeCtrl::-moz-range-track { + background-color: transparent; + outline: none; +} +#zoomCtrl::-moz-range-thumb, #seekCtrl::-moz-range-thumb, #volumeCtrl::-moz-range-thumb { + background-color: transparent; + outline: none; +} + +#zoomCtrl { + width: 80px; + height: 8px; + margin-top: 7px; + + /* removes webkit default styling */ + -webkit-appearance: none; + + border-radius: 6px; + background-color: rgba(255, 255, 255, 0.3); + outline: none; +} + +#seekCtrl { + width: calc(100% - 3%); + height: 5px; + margin: 0px; + + /* removes webkit default styling */ + -webkit-appearance: none; + + background-color: rgba(255, 255, 255, 0.2); + outline: none; +} + +/* thumb pseudo-element */ +#zoomCtrl::-webkit-slider-thumb{ + -webkit-appearance: none; + background-color: white; + outline: none; + width: 12px; + height: 12px; + border-radius: 8px; +} +#zoomCtrl::-moz-range-thumb { + background-color: white; + border: none; + width: 12px; + height: 12px; + border-radius: 8px; +} + +/* thumb pseudo-element */ +#seekCtrl::-webkit-slider-thumb{ + -webkit-appearance: none; + background-color: white; + outline: none; + width: 12px; + height: 12px; + border-radius: 8px; +} +#seekCtrl::-moz-range-thumb { + background-color: white; + border: none; + width: 12px; + height: 12px; + border-radius: 8px; +} + +#volumeCtrl { + display: none; + width: 90px; + height: 8px; + margin: 14px 8px 0px 5px; + + /* removes webkit default styling */ + -webkit-appearance: none; + + border-radius: 6px; + background-color: rgba(255, 255, 255, 0.3); + outline: none; +} + +#volumeArea:hover #volumeCtrl { + display: block +} + +/* thumb pseudo-element */ +#volumeCtrl::-webkit-slider-thumb{ + -webkit-appearance: none; + background-color: white; + outline: none; + width: 12px; + height: 12px; + border-radius: 8px; +} +#volumeCtrl::-moz-range-thumb { + background-color: white; + border: none; + width: 12px; + height: 12px; + border-radius: 8px; +} diff --git a/config/routes.js b/config/routes.js index 6a8e924..0ebd918 100644 --- a/config/routes.js +++ b/config/routes.js @@ -39,6 +39,7 @@ module.exports.routes = { // server rendered webpages 'GET /': 'PageController.showHomePage', 'GET /edit/:id': 'PageController.showEditPage', + 'GET /embed': 'PageController.showPlayerPage', /*************************************************************************** diff --git a/tasks/config/copy.js b/tasks/config/copy.js index a22cbca..7b8047a 100644 --- a/tasks/config/copy.js +++ b/tasks/config/copy.js @@ -31,7 +31,7 @@ module.exports = function(grunt) { }, { expand: true, cwd: './libs', - src: ['angular/angular.js', 'angular-ui-router/release/angular-ui-router.js', + src: ['jquery/dist/jquery.min.js','shaka-player/index.js', 'angular/angular.js', 'angular-ui-router/release/angular-ui-router.js', 'angular-aria/angular-aria.js', 'angular-animate/angular-animate.js', 'angular-material/angular-material.js', 'angular-messages/angular-messages.js', 'clipboard/dist/clipboard.js', 'ngclipboard/dist/ngclipboard.js'], diff --git a/tasks/pipeline.js b/tasks/pipeline.js index b914854..06f8dba 100644 --- a/tasks/pipeline.js +++ b/tasks/pipeline.js @@ -25,6 +25,8 @@ var jsFilesToInject = [ // Load sails.io before everything else 'js/dependencies/sails.io.js', + 'js/dependencies/jquery.min.js', + 'js/dependencies/index.js', 'js/dependencies/angular.js', 'js/dependencies/angular-ui-router.js', 'js/dependencies/angular-material.js', diff --git a/views/layout.ejs b/views/layout.ejs index 70d07b5..793efd2 100644 --- a/views/layout.ejs +++ b/views/layout.ejs @@ -18,6 +18,8 @@ + + @@ -31,6 +33,8 @@ + + diff --git a/views/player.ejs b/views/player.ejs new file mode 100644 index 0000000..1350b19 --- /dev/null +++ b/views/player.ejs @@ -0,0 +1,148 @@ + + + + + + + + + + +
Video Source: + +
+ + + + +
+ +
+
+ + + +
+
+ +
+
+
+ + + + + +
+ 0:00:00 / + 0:00:00 +
+
+
+ + + + +
+
+
+
From 4a72c5fd53bc8e34d323995587ec5f802de23682 Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 23 Feb 2016 15:13:51 +0800 Subject: [PATCH 11/55] Update uploadVideo API to update Video Model's videoDir based on the ID given --- api/controllers/VideoController.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js index b8e0413..e3a36fc 100644 --- a/api/controllers/VideoController.js +++ b/api/controllers/VideoController.js @@ -110,11 +110,21 @@ module.exports = { return res.badRequest('No file was uploaded'); } - return res.json({ - message: uploadedFiles.length + ' file(s) uploaded successfully!', - files: uploadedFiles, - textParams: req.params.all() + // Update the Video Model's videoDir based on the video ID + Video.update({ + id: req.param('id') + }, { + videoDir: uploadedFiles[0].fd + }).exec(function (err, updatedVideo) { + return res.json({ + message: uploadedFiles.length + ' file(s) uploaded successfully!', + // Only upload 1 video per time + files: uploadedFiles[0], + textParams: req.params.all(), + video: updatedVideo[0] + }); }); + }); } }; From 67ce593975482217bcf664fb1d038b7f94731a00 Mon Sep 17 00:00:00 2001 From: linxea Date: Tue, 23 Feb 2016 15:26:00 +0800 Subject: [PATCH 12/55] Move player dependencies to player.ejs --- tasks/pipeline.js | 15 +++------------ views/layout.ejs | 7 ++----- views/player.ejs | 5 ++++- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/tasks/pipeline.js b/tasks/pipeline.js index 06f8dba..2560626 100644 --- a/tasks/pipeline.js +++ b/tasks/pipeline.js @@ -22,11 +22,9 @@ var cssFilesToInject = [ // Client-side javascript files to inject in order // (uses Grunt-style wildcard/glob/splat expressions) var jsFilesToInject = [ - // Load sails.io before everything else 'js/dependencies/sails.io.js', 'js/dependencies/jquery.min.js', - 'js/dependencies/index.js', 'js/dependencies/angular.js', 'js/dependencies/angular-ui-router.js', 'js/dependencies/angular-material.js', @@ -36,16 +34,9 @@ var jsFilesToInject = [ 'js/dependencies/clipboard.js', 'js/dependencies/ngclipboard.js', 'js/public/zoomableApp.js', - 'js/public/directives.js', - 'js/public/controllers/dashboardController.js', - 'js/public/controllers/loginController.js', - - // Dependencies like jQuery, or Angular are brought in here - 'js/dependencies/**/*.js', - - // All of the rest of your client-side js files - // will be injected here in no particular order. - 'js/**/*.js' + 'js/public/directives.js', + 'js/public/servicesAPI.js', + 'js/public/controllers/*.js', ]; diff --git a/views/layout.ejs b/views/layout.ejs index 793efd2..5b43223 100644 --- a/views/layout.ejs +++ b/views/layout.ejs @@ -19,7 +19,6 @@ - @@ -30,12 +29,10 @@ + - - - - + diff --git a/views/player.ejs b/views/player.ejs index 1350b19..8dce1fc 100644 --- a/views/player.ejs +++ b/views/player.ejs @@ -1,5 +1,4 @@ - @@ -146,3 +145,7 @@ + + + + From d45b0e98845f176c0c589984033917df32d69f2d Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 23 Feb 2016 17:01:04 +0800 Subject: [PATCH 13/55] Update name of API to upload in VideoController --- api/controllers/VideoController.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js index e3a36fc..9df5a7f 100644 --- a/api/controllers/VideoController.js +++ b/api/controllers/VideoController.js @@ -95,13 +95,13 @@ module.exports = { }, /** - * `VideoController.uploadVideo()` - * Usage: POST /api/video/uploadVideo + * `VideoController.upload()` + * Usage: POST /api/video/upload * Content: {id: ':id', video: 'attach video file here'} **/ - uploadVideo: function (req, res) { + upload: function (req, res) { req.file('video').upload({ - dirname: sails.config.appPath + '/vid/' + req.param('id') + dirname: sails.config.appPath + '/assets/vid/' + req.param('id') }, function (err, uploadedFiles) { if (err) return res.negotiate(err); From 4e821a6e28c78190c0f1e23328fcf6701c91ed36 Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Tue, 23 Feb 2016 21:35:30 +0800 Subject: [PATCH 14/55] Accept argument inputs for script and shortened code length --- .../video_processing_script.sh | 159 ++++++++---------- 1 file changed, 72 insertions(+), 87 deletions(-) diff --git a/video_processing_script/video_processing_script.sh b/video_processing_script/video_processing_script.sh index bab4ff7..3dd84c8 100644 --- a/video_processing_script/video_processing_script.sh +++ b/video_processing_script/video_processing_script.sh @@ -11,7 +11,7 @@ # the respective 360p, 480p, 720p and 1080p resolutions # Directory on the server where uploaded videos go to -srclocation="/Users/nellystix/Desktop/TEST" +srclocation=$* # Extensions recognizable by FFMPEG and that can be processed srcext=".mp4" # Final output format type @@ -25,18 +25,15 @@ c="C" # Looking for a file that ends in .mp4 and executes the FFMPEG commands to commence processing # Retrieve the video's directory location, including its name (e.g. /usr/bin/test.txt) +# ASSUMPTION: There is only 1 file in that directory with that specified extension vidloc=$(find "${srclocation}" -iname "*${srcext}") +# Retrieve the parent directory of this file +parentdir=$(dirname $vidloc) # Strip out the leading directory prefixes infront of the filename vidname_and_ext=$(basename "$vidloc") # Pure filename without the extension type vidname=$(basename "$vidname_and_ext" ${srcext}) -# Create a folder for all resolutions and cropped parts of the video -mkdir -p -- ${vidname} - -# Initalizing the variables to track the (x,y) coordinates of the row and column for the cropped video -x_coord=0 -y_coord=0 # 360p video details # Additional name identifier for the video in 360p format _360p_ext="_360p" @@ -44,26 +41,11 @@ _360p_ext="_360p" _360p_crop_w=120 # 360p cropped video height _360p_crop_h=120 -# Convert the original video to 360p resolution -ffmpeg -i ${vidname_and_ext} -c:v libx264 -b:v 350k -r 24 -s 480x360 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a libvo_aacenc -b:a 128k -ac 2 ${vidname}${_360p_ext}${finalformat} - -# Crop the 360p video into its respective 4:3 parts -# For each column -for i in {1..4} -do - y_coord=0 - # For each row - for j in {1..3} - do - ffmpeg -i ${vidname}${_360p_ext}${finalformat} -filter:v "crop=${_360p_crop_w}:${_360p_crop_h}:${x_coord}:${y_coord}" ${vidname}${_360p_ext}${r}${j}${c}${i}${finalformat} - y_coord=$((y_coord + _360p_crop_h)) - done - x_coord=$((x_coord + _360p_crop_w)) -done +# 360p FFMPEG conversion command +_360p_cmd() { + ffmpeg -i ${parentdir}${slash}${vidname_and_ext} -c:v libx264 -crf 23 -vf scale=480x360 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a aac -b:a 128k -ac 2 ${parentdir}${slash}${vidname}${_360p_ext}${finalformat} +} -# Reinitialize the variables to track the (x,y) coordinates of the row and column for the cropped video -x_coord=0 -y_coord=0 # 480p video details # Additional name identifier for the video in 480p format _480p_ext="_480p" @@ -71,26 +53,11 @@ _480p_ext="_480p" _480p_crop_w=160 # 480p cropped video height _480p_crop_h=160 -# Convert the original video to 480p resolution -ffmpeg -i ${vidname_and_ext} -c:v libx264 -b:v 750k -r 24 -s 640x480 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a libvo_aacenc -b:a 128k -ac 2 ${vidname}${_480p_ext}${finalformat} - -# Crop the 480p video into its respective 4:3 parts -# For each column -for i in {1..4} -do - y_coord=0 - # For each row - for j in {1..3} - do - ffmpeg -i ${vidname}${_480p_ext}${finalformat} -filter:v "crop=${_480p_crop_w}:${_480p_crop_h}:${x_coord}:${y_coord}" ${vidname}${_480p_ext}${r}${j}${c}${i}${finalformat} - y_coord=$((y_coord + _480p_crop_h)) - done - x_coord=$((x_coord + _480p_crop_w)) -done +# 480p FFMPEG conversion command +_480p_cmd() { + ffmpeg -i ${parentdir}${slash}${vidname_and_ext} -c:v libx264 -crf 23 -vf scale=640x480 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a aac -b:a 128k -ac 2 ${parentdir}${slash}${vidname}${_480p_ext}${finalformat} +} -# Reinitialize the variables to track the (x,y) coordinates of the row and column for the cropped video -x_coord=0 -y_coord=0 # 720p video details # Additional name identifier for the video in 720p format _720p_ext="_720p" @@ -98,26 +65,11 @@ _720p_ext="_720p" _720p_crop_w=320 # 720p cropped video height _720p_crop_h=240 -# Convert the original video to 720p resolution -ffmpeg -i ${vidname_and_ext} -c:v libx264 -b:v 2000k -r 24 -s 1280x720 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a libvo_aacenc -b:a 128k -ac 2 ${vidname}${_720p_ext}${finalformat} +# 720p FFMPEG conversion command +_720p_cmd() { + ffmpeg -i ${parentdir}${slash}${vidname_and_ext} -c:v libx264 -crf 23 -vf scale=1280x720 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a aac -b:a 128k -ac 2 ${parentdir}${slash}${vidname}${_720p_ext}${finalformat} +} -# Crop the 720p video into its respective 4:3 parts -# For each column -for i in {1..4} -do - y_coord=0 - # For each row - for j in {1..3} - do - ffmpeg -i ${vidname}${_720p_ext}${finalformat} -filter:v "crop=${_720p_crop_w}:${_720p_crop_h}:${x_coord}:${y_coord}" ${vidname}${_720p_ext}${r}${j}${c}${i}${finalformat} - y_coord=$((y_coord + _720p_crop_h)) - done - x_coord=$((x_coord + _720p_crop_w)) -done - -# Reinitialize the variables to track the (x,y) coordinates of the row and column for the cropped video -x_coord=0 -y_coord=0 # 1080p video details # Additional name identifier for the video in 1080p format _1080p_ext="_1080p" @@ -125,21 +77,64 @@ _1080p_ext="_1080p" _1080p_crop_w=480 # 1080p cropped video height _1080p_crop_h=360 -# Convert the original video to 1080p resolution -ffmpeg -i ${vidname_and_ext} -c:v libx264 -b:v 5000k -r 24 -s 1920x1080 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a libvo_aacenc -b:a 128k -ac 2 ${vidname}${_1080p_ext}${finalformat} +# 1080p FFMPEG conversion command +_1080p_cmd() { + ffmpeg -i ${parentdir}${slash}${vidname_and_ext} -c:v libx264 -crf 23 -vf scale=1920x1080 -x264opts keyint=48:min-keyint=48:no-scenecut -movflags +faststart -preset slow -profile:v high -c:a aac -b:a 128k -ac 2 ${parentdir}${slash}${vidname}${_1080p_ext}${finalformat} +} + +# Create an array of possible resolutions to convert to +declare -a resArr +resArr[1]=${_360p_ext} +resArr[2]=${_480p_ext} +resArr[3]=${_720p_ext} +resArr[4]=${_1080p_ext} + +# Create an array of cropped widths for each corresponding resolution +declare -a cropWidthArr +cropWidthArr[1]=${_360p_crop_w} +cropWidthArr[2]=${_480p_crop_w} +cropWidthArr[3]=${_720p_crop_w} +cropWidthArr[4]=${_1080p_crop_w} + +# Create an array of cropped heights for each corresponding resolution +declare -a cropHeightArr +cropHeightArr[1]=${_360p_crop_h} +cropHeightArr[2]=${_480p_crop_h} +cropHeightArr[3]=${_720p_crop_h} +cropHeightArr[4]=${_1080p_crop_h} + +# Create an array of commands for each corresponding resolution +declare -a cmdArr +cmdArr[1]=_360p_cmd +cmdArr[2]=_480p_cmd +cmdArr[3]=_720p_cmd +cmdArr[4]=_1080p_cmd -# Crop the 1080p video into its respective 4:3 parts -# For each column +# Initalizing the variables to track the (x,y) coordinates of the row and column for the cropped video +x_coord=0 +y_coord=0 + +# For each resolution for i in {1..4} do - y_coord=0 - # For each row - for j in {1..3} + # Run the resolution conversion command pertaining to that given resolution + ${cmdArr[$i]} + + # Crop the video into its respection 4:3 parts + # For each column + for col in {1..4} do - ffmpeg -i ${vidname}${_1080p_ext}${finalformat} -filter:v "crop=${_1080p_crop_w}:${_1080p_crop_h}:${x_coord}:${y_coord}" ${vidname}${_1080p_ext}${r}${j}${c}${i}${finalformat} - y_coord=$((y_coord + _1080p_crop_h)) + y_coord=0 + # For each row + for row in {1..3} + do + ffmpeg -i ${parentdir}${slash}${vidname}${resArr[$i]}${finalformat} -filter:v "crop=${cropWidthArr[$i]}:${cropHeightArr[$i]}:${x_coord}:${y_coord}" ${parentdir}${slash}${vidname}${resArr[$i]}${r}${row}${c}${col}${finalformat} + y_coord=$((y_coord + cropHeightArr[$i])) + done + x_coord=$((x_coord + cropWidthArr[$i])) done - x_coord=$((x_coord + _1080p_crop_w)) + x_coord=0 + y_coord=0 done # Generate the thumbnail for the video @@ -148,7 +143,7 @@ tn_size="640:360" # Defined thumbnail format tn_format=".png" # Generating the actual thumbnail -ffmpeg -i ${vidname_and_ext} -vf "thumbnail,scale=${tn_size}" -frames:v 1 ${vidname}${tn_format} +ffmpeg -i ${parentdir}${slash}${vidname_and_ext} -vf "thumbnail,scale=${tn_size}" -frames:v 1 ${parentdir}${slash}${vidname}${tn_format} # Create the MPD file for each player (12 in total), comprising of the 360p, 480p, 720p and 1080p versions # MPD extension type @@ -161,19 +156,9 @@ do # For each row for j in {1..3} do - MP4Box -dash 10000 -rap -frag-rap -profile dashavc264:onDemand -out ${vidname}${mpd_name}${r}${j}${c}${i}${mpd_ext} ${vidname}${_360p_ext}${r}${j}${c}${i}${finalformat}#audio ${vidname}${_360p_ext}${r}${j}${c}${i}${finalformat}#video ${vidname}${_480p_ext}${r}${j}${c}${i}${finalformat}#video ${vidname}${_720p_ext}${r}${j}${c}${i}${finalformat}#video ${vidname}${_1080p_ext}${r}${j}${c}${i}${finalformat}#video + MP4Box -dash 10000 -rap -frag-rap -profile dashavc264:onDemand -out ${parentdir}${slash}${vidname}${mpd_name}${r}${j}${c}${i}${mpd_ext} ${parentdir}${slash}${vidname}${_360p_ext}${r}${j}${c}${i}${finalformat}#audio ${parentdir}${slash}${vidname}${_360p_ext}${r}${j}${c}${i}${finalformat}#video ${parentdir}${slash}${vidname}${_480p_ext}${r}${j}${c}${i}${finalformat}#video ${parentdir}${slash}${vidname}${_720p_ext}${r}${j}${c}${i}${finalformat}#video ${parentdir}${slash}${vidname}${_1080p_ext}${r}${j}${c}${i}${finalformat}#video done done -# Move all video files that have the same video name into the folder, EXCEPT the original video -mv *${_360p_ext}* ${vidname} -mv *${_480p_ext}* ${vidname} -mv *${_720p_ext}* ${vidname} -mv *${_1080p_ext}* ${vidname} -# Move the thumbnail of the video into the folder -mv ${vidname}${tn_format} ${vidname} -# Move the MPD files to the folder -mv *${mpd_name}* ${vidname} - # Delete the original uploaded video -rm ${vidname_and_ext} +rm ${parentdir}${slash}${vidname_and_ext} From 0d331585327973939267ad10ff11b42087a56519 Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Fri, 26 Feb 2016 15:17:17 +0800 Subject: [PATCH 15/55] update the package.json file to match that with develop and remove dependency for fluent-ffmpeg (No longer using that) --- package.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4cd70e6..7c999da 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "dependencies": { "bower": "^1.7.2", "ejs": "~0.8.4", - "fluent-ffmpeg": "^2.0.1", "grunt": "0.4.2", "grunt-contrib-clean": "~0.5.0", "grunt-contrib-coffee": "~0.10.1", @@ -30,6 +29,7 @@ "scripts": { "start": "node app.js", "debug": "node debug app.js" + "test": "node ./node_modules/mocha/bin/mocha test/bootstrap.test.js test/integration/**/*.test.js" }, "main": "app.js", "repository": { @@ -39,6 +39,12 @@ "author": "Meteoria", "license": "MIT", "devDependencies": { - "grunt-contrib-jasmine": "^0.9.2" + "barrels": "^1.6.4", + "grunt-contrib-jasmine": "^0.9.2", + "grunt-mocha-test": "^0.12.7", + "mocha": "^2.4.5", + "sails-memory": "^0.10.6", + "should": "^8.2.2", + "supertest": "^1.2.0" } } From 138f69f76410a0a7e0684c28c30d23025f5e31ca Mon Sep 17 00:00:00 2001 From: kphua Date: Mon, 29 Feb 2016 14:29:41 +0800 Subject: [PATCH 16/55] Set up karma test for front-end --- bower.json | 3 ++ karma.conf.js | 86 ++++++++++++++++++++++++++++++++++++++++++ package.json | 10 +++++ tasks/config/karma.js | 20 ++++++++++ tasks/register/test.js | 3 +- 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 karma.conf.js create mode 100644 tasks/config/karma.js diff --git a/bower.json b/bower.json index b6cbc72..41f007f 100644 --- a/bower.json +++ b/bower.json @@ -26,5 +26,8 @@ "angular-material": "~1.0.3", "ngclipboard": "^1.1.0", "clipboard": "^1.5.8" + }, + "devDependencies": { + "angular-mocks": "^1.5.0" } } diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..cfec222 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,86 @@ +// Karma configuration +// Generated on Mon Feb 29 2016 11:42:08 GMT+0800 (SGT) + +module.exports = function(config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['jasmine'], + + + // list of files / patterns to load in the browser + files: [ + 'libs/angular/angular.js', + 'libs/angular-animate/angular-animate.js', + 'libs/angular-aria/angular-aria.js', + 'libs/angular-material/angular-material.js', + 'libs/angular-messages/angular-messages.js', + 'libs/angular-ui-router/release/angular-ui-router.js', + 'libs/angular-mocks/angular-mocks.js', + 'libs/angular-material/angular-material-mocks.js', + 'libs/clipboard/dist/clipboard.js', + 'libs/ngclipboard/dist/ngclipboard.js', + + 'assets/js/public/zoomableApp.js', + 'assets/js/public/directives.js', + 'assets/js/public/servicesAPI.js', + 'assets/js/public/controllers/*.js', + + 'test/frontend/*.spec.js' + ], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['spec'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['PhantomJS'], + //browsers: ['PhantomJS', 'Chrome', 'Safari', 'Firefox'], + + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity + }) +} diff --git a/package.json b/package.json index a7d98da..65f8081 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,18 @@ "devDependencies": { "barrels": "^1.6.4", "grunt-contrib-jasmine": "^0.9.2", + "grunt-karma": "^0.12.1", "grunt-mocha-test": "^0.12.7", + "jasmine-core": "^2.4.1", + "karma": "^0.13.21", + "karma-chrome-launcher": "^0.2.2", + "karma-firefox-launcher": "^0.1.7", + "karma-jasmine": "^0.3.7", + "karma-phantomjs-launcher": "^1.0.0", + "karma-safari-launcher": "^0.1.1", + "karma-spec-reporter": "0.0.24", "mocha": "^2.4.5", + "phantomjs-prebuilt": "^2.1.4", "sails-memory": "^0.10.6", "should": "^8.2.2", "supertest": "^1.2.0" diff --git a/tasks/config/karma.js b/tasks/config/karma.js new file mode 100644 index 0000000..2983acc --- /dev/null +++ b/tasks/config/karma.js @@ -0,0 +1,20 @@ +/** + * Run karma test cases + * + * --------------------------------------------------------------- + * + * A grunt task for running frontend unit testing using karma + * + * For usage docs see: + * https://github.com/karma-runner/grunt-karma + */ +module.exports = function(grunt) { + + grunt.config.set('karma', { + unit: { + configFile: 'karma.conf.js' + } + }); + + grunt.loadNpmTasks('grunt-karma'); +}; diff --git a/tasks/register/test.js b/tasks/register/test.js index da7382b..e87e766 100644 --- a/tasks/register/test.js +++ b/tasks/register/test.js @@ -1,5 +1,6 @@ module.exports = function (grunt) { grunt.registerTask('test', [ - 'mochaTest' + 'mochaTest', + 'karma' ]); }; \ No newline at end of file From 0a9d5b58d4b12a997f5affcf681747be114b4a4b Mon Sep 17 00:00:00 2001 From: linxea Date: Mon, 29 Feb 2016 15:06:34 +0800 Subject: [PATCH 17/55] Add delete icon --- assets/images/ic_delete_black_24px.svg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 assets/images/ic_delete_black_24px.svg diff --git a/assets/images/ic_delete_black_24px.svg b/assets/images/ic_delete_black_24px.svg new file mode 100644 index 0000000..f464281 --- /dev/null +++ b/assets/images/ic_delete_black_24px.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file From bb080cdad4a434a4d71fe5a91dce4af6035d6e7f Mon Sep 17 00:00:00 2001 From: linxea Date: Mon, 29 Feb 2016 15:07:30 +0800 Subject: [PATCH 18/55] Update views in dashboard to prevent confusion --- .../public/controllers/dashboardController.js | 100 +++++++----------- assets/styles/custom/dashboard.less | 2 +- views/dashboard.ejs | 33 ++++-- 3 files changed, 59 insertions(+), 76 deletions(-) diff --git a/assets/js/public/controllers/dashboardController.js b/assets/js/public/controllers/dashboardController.js index 09015ed..26b5599 100644 --- a/assets/js/public/controllers/dashboardController.js +++ b/assets/js/public/controllers/dashboardController.js @@ -2,13 +2,13 @@ angular.module('zoomableApp').controller('dashboardController', function($scope, // VARIABLES $scope.defaultImagePath = 'images/bunny.png'; - $scope.iconPrivacyPath = 'images/ic_lock_black_24px.svg'; - $scope.iconTagPath = 'images/ic_tag_black_24px.svg'; - $scope.iconViewPath = 'images/ic_remove_red_eye_black_24px.svg'; - $scope.filterStates = ['Newest','Popular','Public','Private']; - $scope.buttonStates = ['Public','Private','Delete']; + $scope.filterStates = ['Public','Private']; + $scope.sortStates = ['Lastest','Most Viewed']; + $scope.deleteButton = 'Delete Video'; $scope.userFilterState = ''; - $scope.userButtonState = ''; + $scope.userSortState = ''; + $scope.hasMouseover = 'hidden'; + var PUBLIC = 0; var PRIVATE = 1; var DELETE = 2; @@ -19,7 +19,7 @@ angular.module('zoomableApp').controller('dashboardController', function($scope, selectedVideoList : [] } - getVideoList(); + getVideoList(); /* Checkbox Handler */ $scope.isSelectAll = function() { @@ -27,9 +27,9 @@ angular.module('zoomableApp').controller('dashboardController', function($scope, if($scope.master) { for(var i=0;i<$scope.videoList.length;i++) { - $scope.model.selectedVideoList.push($scope.videoList[i].id); + $scope.model.selectedVideoList.push($scope.videoList[i].id); } - } + } angular.forEach($scope.videoList, function (video) { video.selected = $scope.master; @@ -44,92 +44,64 @@ angular.module('zoomableApp').controller('dashboardController', function($scope, $scope.master = false; var index = $scope.model.selectedVideoList.indexOf(id); $scope.model.selectedVideoList.splice(index, 1); - } - } + } + } - /* Dialog Handler */ - $scope.showConfirm = function(ev, buttonState) { - // Check if at least 1 video is checked + /* Dialog Handler for Delete Action */ + $scope.showConfirmDelete = function(ev) { + // Check if at least 1 video is checked if($scope.model.selectedVideoList.length > 0) { - $scope.userButtonState = buttonState; var MESSAGE_VIDEO = 'videos'; // Check plural for confirm dialog text if($scope.model.selectedVideoList.length === 1) { MESSAGE_VIDEO = MESSAGE_VIDEO.substring(0, MESSAGE_VIDEO.length - 1); - } + } // DIALOGUE MESSAGES - var MESSAGE_TITLE_PRIVATE = 'Make Video Private?'; - var MESSAGE_TITLE_PUBLIC = 'Make Video Public?'; var MESSAGE_TITLE_DELETE = 'Delete Video?'; - var MESSAGE_TEXT_CONTENT_PRIVATE = 'Are you sure you want to set ' + $scope.model.selectedVideoList.length + ' ' + MESSAGE_VIDEO + ' to Private?'; - var MESSAGE_TEXT_CONTENT_PUBLIC = 'Are you sure you want to set ' + $scope.model.selectedVideoList.length + ' ' + MESSAGE_VIDEO + ' to Public?'; var MESSAGE_TEXT_CONTENT_DELETE = 'Are you sure you want to delete ' + $scope.model.selectedVideoList.length + ' ' + MESSAGE_VIDEO + '?'; - var MESSAGE_TITLE = ''; - var MESSAGE_TEXT_CONTENT = ''; - - if(buttonState === $scope.buttonStates[PRIVATE]) { - MESSAGE_TITLE = MESSAGE_TITLE_PRIVATE; - MESSAGE_TEXT_CONTENT = MESSAGE_TEXT_CONTENT_PRIVATE; - } else if (buttonState === $scope.buttonStates[PUBLIC]) { - MESSAGE_TITLE = MESSAGE_TITLE_PUBLIC; - MESSAGE_TEXT_CONTENT = MESSAGE_TEXT_CONTENT_PUBLIC; - } else if (buttonState === $scope.buttonStates[DELETE]) { - MESSAGE_TITLE = MESSAGE_TITLE_DELETE; - MESSAGE_TEXT_CONTENT = MESSAGE_TEXT_CONTENT_DELETE; - } - // Appending dialog to document.body to cover sidenav in docs app var confirm = $mdDialog.confirm() - .title(MESSAGE_TITLE) - .textContent(MESSAGE_TEXT_CONTENT) + .title(MESSAGE_TITLE_DELETE) + .textContent(MESSAGE_TEXT_CONTENT_DELETE) .ariaLabel('Confirm Dialog') .targetEvent(ev) .ok('Confirm') .cancel('Cancel'); $mdDialog.show(confirm).then(function() { - if(buttonState === $scope.buttonStates[PRIVATE]) { - for(var i=0;i<$scope.model.selectedVideoList.length;i++) { - servicesAPI.update($scope.model.selectedVideoList[i], {privacy: PRIVATE}).then(function() { - getVideoList(); - }); - } - } else if (buttonState === $scope.buttonStates[PUBLIC]) { - for(var i=0;i<$scope.model.selectedVideoList.length;i++) { - servicesAPI.update($scope.model.selectedVideoList[i], {privacy: PUBLIC}).then(function() { - getVideoList(); - }); - } - } else if (buttonState === $scope.buttonStates[DELETE]) { for(var i=0;i<$scope.model.selectedVideoList.length;i++) { servicesAPI.delete($scope.model.selectedVideoList[i]).then(function() { getVideoList(); }); - } - } - // Empty video list + } + // Empty video list $scope.model.selectedVideoList = []; }); - } else { return; } }; /* Sort video list according to filter states */ - $scope.updateFilterState = function (state) { - $scope.userFilterState = state; - if ($scope.userFilterState === 'Newest') { + $scope.updateSortState = function (state) { + $scope.userSortState = state; + if ($scope.userSortState === 'Lastest') { $scope.sortType = '-createdAt'; - } else if ($scope.userFilterState === 'Popular') { + } else if ($scope.userSortState === 'Most Viewed') { $scope.sortType = '-views'; - } else if ($scope.userFilterState === 'Public') { - $scope.sortType = 'privacy'; + } + }; + + /* Sort video list according to filter states */ + $scope.updateFitlerState = function (state) { + $scope.userFilterState = state; + if ($scope.userFilterState === 'Public') { + $scope.filterType = 'privacy'; } else if ($scope.userFilterState === 'Private') { - $scope.sortType = '-privacy'; + $scope.filterType = '-privacy'; } }; @@ -141,7 +113,7 @@ angular.module('zoomableApp').controller('dashboardController', function($scope, }) .error(function(data) { console.log('Error: ' + data); - }); + }); } /* To display confirmation dialog */ @@ -165,16 +137,16 @@ angular.module('zoomableApp').controller('dashboardController', function($scope, title : file.name, videoDir : uploadUrl, thumbnailDir : uploadUrl - }; + }; servicesAPI.create(videoData) .success(function(data) { videoData = {}; - getVideoList(); + getVideoList(); }) .error(function(data) { console.log('Error: ' + data); - }); + }); } }; diff --git a/assets/styles/custom/dashboard.less b/assets/styles/custom/dashboard.less index 86af0ff..98ea13e 100644 --- a/assets/styles/custom/dashboard.less +++ b/assets/styles/custom/dashboard.less @@ -18,7 +18,7 @@ } .md-avatar-icon { - padding: 0 !important; + padding: 0 20px !important; } md-checkbox.select-all { diff --git a/views/dashboard.ejs b/views/dashboard.ejs index 5138401..bd3796b 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -27,20 +27,30 @@
- - {{ buttonState }} + + {{ deleteButton }}
- + + + {{ sortState }} + + +
+ +
+ + {{ filterState }}
+ @@ -48,7 +58,7 @@ - + + + +
+ +
+
+ + + +
+
+ +
+
+
+ + + + + +
+ 0:00:00 / + 0:00:00 +
+
+
+ + + + +
+
+
+
+ + + + From def053712957e12229e356e8b72d4b3b13502977 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 11:29:40 +0800 Subject: [PATCH 33/55] Specifically specify mpd source (to update again once able to upload video to server) --- assets/js/public/player/initShakaPlayer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/assets/js/public/player/initShakaPlayer.js b/assets/js/public/player/initShakaPlayer.js index b586ccc..21f9074 100644 --- a/assets/js/public/player/initShakaPlayer.js +++ b/assets/js/public/player/initShakaPlayer.js @@ -17,7 +17,9 @@ function initShakaPlayer() { }); // Construct a DashVideoSource to represent the DASH manifest. - var mpdUrl = $('#mpdSource').val(); + //var mpdUrl = $('#mpdSource').val(); + // use a custom MPD source for now + var mpdUrl = 'http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car-20120827-manifest.mpd'; var estimator = new shaka.util.EWMABandwidthEstimator(); var source = new shaka.player.DashVideoSource(mpdUrl, null, estimator); From 7c58726df57982d4d234464681966ee292a634ea Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 11:33:34 +0800 Subject: [PATCH 34/55] Initialise shaka player in player.js file --- assets/js/public/player/player.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/js/public/player/player.js b/assets/js/public/player/player.js index 93f9586..aba3017 100644 --- a/assets/js/public/player/player.js +++ b/assets/js/public/player/player.js @@ -478,7 +478,8 @@ var Player = function(vid,canv) { } document.addEventListener('DOMContentLoaded', function() { - var zoomable = new Player(document.getElementById('video'), document.getElementById('canvas')); + initShakaPlayer(); + var zoomable = new Player(document.getElementById('video'), document.getElementById('canvas')); zoomable.init(); }, false); From 1d792d469c1700621587aacdca5dc06d3db85099 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 11:34:26 +0800 Subject: [PATCH 35/55] Remove redundant css file from previous player --- assets/styles/importer.less | 2 +- assets/styles/player/canvas.css.less | 261 --------------------------- 2 files changed, 1 insertion(+), 262 deletions(-) delete mode 100644 assets/styles/player/canvas.css.less diff --git a/assets/styles/importer.less b/assets/styles/importer.less index d2f12e8..e0934d2 100644 --- a/assets/styles/importer.less +++ b/assets/styles/importer.less @@ -35,4 +35,4 @@ @import 'custom/edit.less'; @import 'custom/login.less'; -@import 'player/canvas.css.less'; +@import 'player/canvas.less'; diff --git a/assets/styles/player/canvas.css.less b/assets/styles/player/canvas.css.less deleted file mode 100644 index afa6f6d..0000000 --- a/assets/styles/player/canvas.css.less +++ /dev/null @@ -1,261 +0,0 @@ -#canvasPlayerArea { - position: relative; - width: 640px; - height: 360px; -} - -#uiControls { - width: inherit; -} -#uiControls.hideOnHover { - opacity: 0; - transition: opacity 0.4s; -} -#canvasPlayerArea:hover #uiControls.hideOnHover { - opacity: 1; -} - -#canvas { - width: inherit; - height: inherit; - position: relative; - z-index: 0; -} - -#zoomBarControls { - position: absolute; - /*top: 50%; - left: 86%;*/ - top: 75%; - right: -100px; - transform-origin: 0% 0%; - z-index: 10; - background-color: rgba(0, 0, 0, 0.5); - width: 135px; - height: 24px; - transform: rotate(270deg); - -webkit-transform: rotate(270deg); /* safari */ - -moz-transform: rotate(270deg); /* firefox */ - display: flex; - justify-content: space-between; -} - -#zoomBarControls.fs-adjust { - top: 65%; - right: -90px; -} - -#zoomBarControls button { - background-size: 24px 24px; - border: none; - outline: none; - width: 24px; - height: 24px; - opacity: 0.85; - transform: rotate(180deg); - -webkit-transform: rotate(180deg); /* safari */ - -moz-transform: rotate(180deg); /* firefox */ -} -#zoomBarControls button:hover { - opacity: 1; -} - -#seekBarControls { - position: absolute; - display: flex; - bottom: 36px; - left: 0px; - z-index: 10; - width: inherit; - height: 12px; - background-color: rgba(0, 0, 0, 0.5); - justify-content: space-around; - align-items: flex-end; -} - -#bottomBarControls { - position: absolute; - display: flex; - bottom: 0; - left: 0; - width: inherit; - height: 36px; - background-color: rgba(0, 0, 0, 0.5); - justify-content: space-between; -} - -.flexChild { - display: flex; - padding: 0px 15px; -} - -#bottomBarControls button { - background-size: 32px 32px; - border: none; - outline: none; - margin-top: 2px; - width: 32px; - height: 32px; - opacity: 0.85; -} -#bottomBarControls button:hover { - opacity: 1; -} - -#volumeArea { - display: flex; - padding-right: 5px; -} - -#zoomInBtn { - background: url(../images/ic_zoom_in.svg) no-repeat; -} -#zoomOutBtn { - background: url(../images/ic_zoom_out.svg) no-repeat; -} -#playPauseBtn.play { - background: url(../images/ic_play.svg) no-repeat; -} -#playPauseBtn.pause { - background: url(../images/ic_pause.svg) no-repeat; -} -#playPauseBtn.replay { - background: url(../images/ic_replay.svg) no-repeat; -} -#volumeBtn.low{ - background: url(../images/ic_volume_low.svg) no-repeat; -} -#volumeBtn.high{ - background: url(../images/ic_volume_high.svg) no-repeat; -} -#volumeBtn.off{ - background: url(../images/ic_volume_off.svg) no-repeat; -} -#snapshotBtn { - background: url(../images/ic_snapshot.svg) no-repeat; -} -#settingBtn { - background: url(../images/ic_setting.svg) no-repeat; -} -#captionBtn { - background: url(../images/ic_caption.svg) no-repeat; -} -#fullscreenBtn { - background: url(../images/ic_fullscreen.svg) no-repeat; -} -#fullscreenBtn.exit { - background: url(../images/ic_fullscreen_exit.svg) no-repeat; -} - -#timeTxt { - padding: 12px 8px 0px 5px; - font-size: 11px; - font-family: 'Roboto', sans-serif; - color: white; -} - -/* RANGE SLIDER STYLES */ - -/* removes mozilla default styling */ -#zoomCtrl::-moz-range-track, #seekCtrl::-moz-range-track, #volumeCtrl::-moz-range-track { - background-color: transparent; - outline: none; -} -#zoomCtrl::-moz-range-thumb, #seekCtrl::-moz-range-thumb, #volumeCtrl::-moz-range-thumb { - background-color: transparent; - outline: none; -} - -#zoomCtrl { - width: 80px; - height: 8px; - margin-top: 7px; - - /* removes webkit default styling */ - -webkit-appearance: none; - - border-radius: 6px; - background-color: rgba(255, 255, 255, 0.3); - outline: none; -} - -#seekCtrl { - width: calc(100% - 3%); - height: 5px; - margin: 0px; - - /* removes webkit default styling */ - -webkit-appearance: none; - - background-color: rgba(255, 255, 255, 0.2); - outline: none; -} - -/* thumb pseudo-element */ -#zoomCtrl::-webkit-slider-thumb{ - -webkit-appearance: none; - background-color: white; - outline: none; - width: 12px; - height: 12px; - border-radius: 8px; -} -#zoomCtrl::-moz-range-thumb { - background-color: white; - border: none; - width: 12px; - height: 12px; - border-radius: 8px; -} - -/* thumb pseudo-element */ -#seekCtrl::-webkit-slider-thumb{ - -webkit-appearance: none; - background-color: white; - outline: none; - width: 12px; - height: 12px; - border-radius: 8px; -} -#seekCtrl::-moz-range-thumb { - background-color: white; - border: none; - width: 12px; - height: 12px; - border-radius: 8px; -} - -#volumeCtrl { - display: none; - width: 90px; - height: 8px; - margin: 14px 8px 0px 5px; - - /* removes webkit default styling */ - -webkit-appearance: none; - - border-radius: 6px; - background-color: rgba(255, 255, 255, 0.3); - outline: none; -} - -#volumeArea:hover #volumeCtrl { - display: block -} - -/* thumb pseudo-element */ -#volumeCtrl::-webkit-slider-thumb{ - -webkit-appearance: none; - background-color: white; - outline: none; - width: 12px; - height: 12px; - border-radius: 8px; -} -#volumeCtrl::-moz-range-thumb { - background-color: white; - border: none; - width: 12px; - height: 12px; - border-radius: 8px; -} From 72066cbe66da163270779f07280c539414861ae8 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 11:35:27 +0800 Subject: [PATCH 36/55] Show player in edit page --- assets/styles/custom/elements.less | 2 +- assets/styles/player/canvas.less | 16 +++++ views/edit.ejs | 99 +++++++++++++++--------------- 3 files changed, 65 insertions(+), 52 deletions(-) diff --git a/assets/styles/custom/elements.less b/assets/styles/custom/elements.less index cee55b0..7b3cd62 100644 --- a/assets/styles/custom/elements.less +++ b/assets/styles/custom/elements.less @@ -3,7 +3,7 @@ */ body { - min-width: 800px; + min-width: 865px; } md-card { diff --git a/assets/styles/player/canvas.less b/assets/styles/player/canvas.less index b3e88cb..1f74d52 100644 --- a/assets/styles/player/canvas.less +++ b/assets/styles/player/canvas.less @@ -259,3 +259,19 @@ video, canvas, #fullscreenBtn.exit { background: url(../images/ic_fullscreen_exit.svg) no-repeat; } + +/* Fix styles when embed in edit page */ +#edit { + #zoomBarControls { + top: 70%; + right: -100px; + } + + #seekBarControls { + bottom: 40px; + } + + #bottomBarControls { + bottom: 4px; + } +} diff --git a/views/edit.ejs b/views/edit.ejs index 03cc665..ffef035 100644 --- a/views/edit.ejs +++ b/views/edit.ejs @@ -21,60 +21,57 @@
-
- - {{ originalVideoTitle }} -
-
-
-
-
- Cancel - Save +
+ + <%- partial('fullplayer.ejs') %> +
+
+
+
+
+ Cancel + Save +
+
+
+
+

Video Information

+

+ Time Uploaded: + {{ video.createdAt | date:'MMMM d, y h:mma' }} +

+

+ Video Duration: + {{ video.duration }} +

+
-
-
-
-

Video Information

-

- Time Uploaded: - {{ video.createdAt | date:'MMMM d, y h:mma' }} -

-

- Video Duration: - {{ video.duration }} -

-
-
-
-
-
- - - -
-
This is required.
-
-
- - - - -
-
- +
+
+ + + + +
+
This is required.
+
+
+ + + + +
+
+
From fd21742dcf5cf888b9826586ffda0e7c15b1fbdf Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 1 Mar 2016 13:43:00 +0800 Subject: [PATCH 37/55] Rename the scripts/folder and script name --- .../video_processing_script.sh => scripts/video-processing.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename video_processing_script/video_processing_script.sh => scripts/video-processing.sh (100%) diff --git a/video_processing_script/video_processing_script.sh b/scripts/video-processing.sh similarity index 100% rename from video_processing_script/video_processing_script.sh rename to scripts/video-processing.sh From 1663f81569a951ddbcdc42aad3e10a19f2c8c170 Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 1 Mar 2016 13:46:56 +0800 Subject: [PATCH 38/55] Update the script usage --- scripts/video-processing.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/video-processing.sh b/scripts/video-processing.sh index 3dd84c8..2cdfdb5 100644 --- a/scripts/video-processing.sh +++ b/scripts/video-processing.sh @@ -10,6 +10,8 @@ # This script will be executed on the server side to automate the cropping and changing the resolution of videos into # the respective 360p, 480p, 720p and 1080p resolutions +# Usage: video-processing.sh 'video-source-location' + # Directory on the server where uploaded videos go to srclocation=$* # Extensions recognizable by FFMPEG and that can be processed From 7fbd72cd8998b52c87ebda13b05d627487a5818c Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 1 Mar 2016 13:56:56 +0800 Subject: [PATCH 39/55] Update the VideoProcessingService to run .sh --- api/services/VideoProcessingService.js | 36 ++++++++++++++++++-------- package.json | 1 - scripts/video-processing.sh | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/api/services/VideoProcessingService.js b/api/services/VideoProcessingService.js index 2aba4cd..c953209 100644 --- a/api/services/VideoProcessingService.js +++ b/api/services/VideoProcessingService.js @@ -5,23 +5,37 @@ * @help :: See http://links.sailsjs.org/docs/service */ -var fs = require('fs'); -var ffmpeg = require('fluent-ffmpeg'); +// var fs = require('fs'); +var spawn = require('child_process').spawn; module.exports = { automateProcessing: function(options) { - // Type your code here // To get dir, use options.dir dirPath = options.dir; - var videoFile = fs.readdir(dirPath, function(error, listOfVids) { - // If an error has occurred - if (error) { - // Print the error for now, throw an error for a error handler later - console.log(error) - } - }) + // var videoFile = fs.readdir(dirPath, function(error, listOfVids) { + // // If an error has occurred + // if (error) { + // // Print the error for now, throw an error for a error handler later + // console.log(error) + // } + // }) + + var command = spawn(sails.config.appPath + '/scripts/video-processing.sh', [dirPath]); + + command.stdout.on('data', function(chunk) { + console.log('stdout: ' + chunk); + }); + + command.stderr.on('data', function(chunk) { + console.log('stderr: ' + chunk); + res.send(500); // when script fails, generate a Server Error HTTP res + }); + + command.on('close', function(chunk) { + res.send("Complete processing the uploaded video!"); + }); - console.log(videoFile[0]); + // console.log(videoFile[0]); } }; \ No newline at end of file diff --git a/package.json b/package.json index 466345e..65f8081 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "grunt-sails-linker": "~0.9.5", "grunt-sync": "~0.0.4", "include-all": "~0.1.3", - "mp4box": "^0.3.8", "machinepack-passwords": "^2.3.0", "rc": "~0.5.0", "sails": "~0.11.0", diff --git a/scripts/video-processing.sh b/scripts/video-processing.sh index 2cdfdb5..a197164 100644 --- a/scripts/video-processing.sh +++ b/scripts/video-processing.sh @@ -26,7 +26,7 @@ r="_R" c="C" # Looking for a file that ends in .mp4 and executes the FFMPEG commands to commence processing -# Retrieve the video's directory location, including its name (e.g. /usr/bin/test.txt) +# Retrieve the video's directory location, including its name (e.g. /vid/:vid_id/test.mp4) # ASSUMPTION: There is only 1 file in that directory with that specified extension vidloc=$(find "${srclocation}" -iname "*${srcext}") # Retrieve the parent directory of this file From 0e7465d96fd384616348104594d83a69961af1bc Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 1 Mar 2016 14:05:21 +0800 Subject: [PATCH 40/55] Change the upload folder dir --- api/controllers/VideoController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js index 9df5a7f..1ddb44f 100644 --- a/api/controllers/VideoController.js +++ b/api/controllers/VideoController.js @@ -101,7 +101,7 @@ module.exports = { **/ upload: function (req, res) { req.file('video').upload({ - dirname: sails.config.appPath + '/assets/vid/' + req.param('id') + dirname: sails.config.appPath + '/.tmp/public/upload/vid/' + req.param('id') }, function (err, uploadedFiles) { if (err) return res.negotiate(err); From b46fd4a938f4fc35cb8d5098deece0a56de28dc2 Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Tue, 1 Mar 2016 14:38:09 +0800 Subject: [PATCH 41/55] edit the README.md to reflect more accurate and detailed instructions --- README.md | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index ec52cf1..5afc61f 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,67 @@ # zoomable.js [![Build Status][travis-image]][travis-url] -An open source Javascript-based zoomable video player and server, which utilizes websocket and DASH to stream videos for playback. A zoomable video player is a video player that allows user to zoom into and pan around the video to view selected in higher resolution and in more details. - +This is an open-source HTML5 video player that allows users to zoom in with greater detail and pan around the video with bandwith efficiency. The Javascript-based zoomable video player streams dash-compliant videos and adapts to network conditions to provide continuous video playback by reducing the video resolutions where necessary. ## Installation -### Prerequisites +### Step 1: Cloning the project + +Pull a copy of this project from GitHub (link above) or download the project as a zipped folder (Download ZIP above) to your computer. + +### Step 2: Installing the necessary pre-requisites + +Note: Some of these commands may require permissions to install the relevant packages. As such adding 'sudo' before the actual command (e.g. npm) may help. + +#### Install Node.js on your machine [Node.js](http://nodejs.org) Install Node.js from https://nodejs.org/en/download/ -If you have Homebrew installed: +If you have Homebrew installed on your machine (e.g. Mac OSX): ```bash $ brew install node ``` +OR + +If the MSI installer option is available for your OS: +Download the MSI installer from the Node.js website and run the installer + +#### Redirecting the directory + +Change the current working directory from the command-line interface to that of the project folder + +For example (Terminal on Mac OSX): +```bash +$ cd Desktop\zoomable.js +``` + +#### Install Grunt [Grunt](http://gruntjs.com/) + +Run the Node Package Manager (npm) installation command for the Grunt package: ```bash $ npm install -g grunt-cli -``` +``` + +#### Install Bower [Bower](http://www.npmjs.com/package/bower) + +and the Bower package: ```bash $ npm install -g bower ``` +#### Install Sails.js + [Sails.js](http://sailsjs.org/get-started) + +as well as the Sails.js package: ```bash $ npm install -g sails ``` - ### Setup Install the necessary packages. From 495634963e9a29503766a4339d0c9535250a6460 Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Tue, 1 Mar 2016 14:49:47 +0800 Subject: [PATCH 42/55] update the installation and server setup steps --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5afc61f..9f0afa7 100644 --- a/README.md +++ b/README.md @@ -62,31 +62,30 @@ as well as the Sails.js package: $ npm install -g sails ``` -### Setup - -Install the necessary packages. +### Step 3: Install the other dependencies and libraries +Running the npm and bower install commands will install the dependencies and libraries that the project requires. These dependencies and libraries are indicated inside package.json (for npm install) and bower.json (for bower install). ```bash $ npm install $ bower install ``` -### Task Automation - -Generate frontend assets using Grunt. +### Step 4: Generate latest front-end assets +Running the Grunt command will update the changes made to the front-end assets and compile it for further use later. ```bash $ grunt ``` -### Start server - -Run on http://localhost:1337 . +### Step 5: Starting the server +Start the server by running the Sails command: ```bash $ sails lift ``` +If this server is setup locally, open your browser and enter the url: http://localhost:1337 to visit the main webpage of the project. + ## Usage `to be updated` From 9273889d2e59fd2e4d31e524702b41ecbc66009f Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 14:59:10 +0800 Subject: [PATCH 43/55] Update aspect ration of video in edit page, and resizes correctly --- assets/styles/custom/elements.less | 28 ++++++++++++++++++++++++++++ assets/styles/player/canvas.less | 20 +++++++++++--------- views/edit.ejs | 11 ++++++++--- 3 files changed, 47 insertions(+), 12 deletions(-) diff --git a/assets/styles/custom/elements.less b/assets/styles/custom/elements.less index 7b3cd62..1cd1ddb 100644 --- a/assets/styles/custom/elements.less +++ b/assets/styles/custom/elements.less @@ -38,3 +38,31 @@ md-menu-content { vertical-align: middle; } } + +/* Styling to maintain aspect-ratio of video div + * Adapted from http://jsbin.com/eqeseb/2/edit */ +.wrapper { + position: relative; + width: 100%; +} + +.three-by-two.aspect-ratio { + padding-bottom: 66.666% +} + +.four-by-three.aspect-ratio { + padding-bottom: 75% +} + +.sixteen-by-nine.aspect-ratio { + padding-bottom: 56.25% +} + +.content { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding-right: 10px; +} diff --git a/assets/styles/player/canvas.less b/assets/styles/player/canvas.less index 1f74d52..b53ce5b 100644 --- a/assets/styles/player/canvas.less +++ b/assets/styles/player/canvas.less @@ -16,6 +16,10 @@ video, canvas, } } +#canvasPlayerArea { + background-color: @color-black; +} + #uiControls { width: 100%; @@ -262,16 +266,14 @@ video, canvas, /* Fix styles when embed in edit page */ #edit { - #zoomBarControls { - top: 70%; - right: -100px; + video, canvas, + #canvasPlayerArea { + width: 100%; + height: 100%; } - #seekBarControls { - bottom: 40px; - } - - #bottomBarControls { - bottom: 4px; + #zoomBarControls { + top: 68%; + right: -100px; } } diff --git a/views/edit.ejs b/views/edit.ejs index ffef035..69241a2 100644 --- a/views/edit.ejs +++ b/views/edit.ejs @@ -21,11 +21,16 @@
-
+
- <%- partial('fullplayer.ejs') %> +
+
+
+ <%- partial('fullplayer.ejs') %> +
+
-
+
From fef501780829ec5351396e442f3fdeb3708cca10 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 14:59:40 +0800 Subject: [PATCH 44/55] Update size of video thumbnail image in dashboard to correct aspect ratio --- assets/styles/custom/size.less | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/styles/custom/size.less b/assets/styles/custom/size.less index 602a3d9..65965b9 100644 --- a/assets/styles/custom/size.less +++ b/assets/styles/custom/size.less @@ -4,5 +4,5 @@ @navbar-height: 60px; -@dashboard-video-height: 100px; -@dashboard-video-width: 140px; \ No newline at end of file +@dashboard-video-height: 110px; +@dashboard-video-width: 196px; From 6864136a8db22f9e2bb1707267c86c5357c52d13 Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Tue, 1 Mar 2016 15:03:46 +0800 Subject: [PATCH 45/55] add the package links into the sub header --- README.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 9f0afa7..ee4ffa8 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,7 @@ Pull a copy of this project from GitHub (link above) or download the project as Note: Some of these commands may require permissions to install the relevant packages. As such adding 'sudo' before the actual command (e.g. npm) may help. -#### Install Node.js on your machine - -[Node.js](http://nodejs.org) - -Install Node.js from https://nodejs.org/en/download/ +#### Install [Node.js](http://nodejs.org) on your machine If you have Homebrew installed on your machine (e.g. Mac OSX): ```bash @@ -35,27 +31,21 @@ For example (Terminal on Mac OSX): $ cd Desktop\zoomable.js ``` -#### Install Grunt - -[Grunt](http://gruntjs.com/) +#### Install [Grunt](http://gruntjs.com/) Run the Node Package Manager (npm) installation command for the Grunt package: ```bash $ npm install -g grunt-cli ``` -#### Install Bower - -[Bower](http://www.npmjs.com/package/bower) +#### Install [Bower](http://www.npmjs.com/package/bower) and the Bower package: ```bash $ npm install -g bower ``` -#### Install Sails.js - -[Sails.js](http://sailsjs.org/get-started) +#### Install [Sails.js](http://sailsjs.org/get-started) as well as the Sails.js package: ```bash From 8d094f8d388b712d2ee27efd9125003c84b03521 Mon Sep 17 00:00:00 2001 From: linxea Date: Tue, 1 Mar 2016 15:12:22 +0800 Subject: [PATCH 46/55] Remove upload path in grunt clean --- api/controllers/VideoController.js | 3 ++- api/services/VideoProcessingService.js | 3 +-- tasks/config/clean.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js index 1ddb44f..7b5c094 100644 --- a/api/controllers/VideoController.js +++ b/api/controllers/VideoController.js @@ -101,7 +101,8 @@ module.exports = { **/ upload: function (req, res) { req.file('video').upload({ - dirname: sails.config.appPath + '/.tmp/public/upload/vid/' + req.param('id') + dirname: sails.config.appPath + '/.tmp/public/upload/vid/' + req.param('id'), + maxBytes: 2 * 1000 * 1000 * 1000 }, function (err, uploadedFiles) { if (err) return res.negotiate(err); diff --git a/api/services/VideoProcessingService.js b/api/services/VideoProcessingService.js index c953209..ddb997d 100644 --- a/api/services/VideoProcessingService.js +++ b/api/services/VideoProcessingService.js @@ -20,7 +20,6 @@ module.exports = { // console.log(error) // } // }) - var command = spawn(sails.config.appPath + '/scripts/video-processing.sh', [dirPath]); command.stdout.on('data', function(chunk) { @@ -35,7 +34,7 @@ module.exports = { command.on('close', function(chunk) { res.send("Complete processing the uploaded video!"); }); - + // console.log(videoFile[0]); } }; \ No newline at end of file diff --git a/tasks/config/clean.js b/tasks/config/clean.js index 8cfc9bb..eb7d4c2 100644 --- a/tasks/config/clean.js +++ b/tasks/config/clean.js @@ -12,7 +12,7 @@ module.exports = function(grunt) { grunt.config.set('clean', { - dev: ['.tmp/public/**'], + dev: ['.tmp/public/js','.tmp/public/styles'], build: ['www'] }); From bcbf02e1e5e546f46361ce41cfc65e76798b7b96 Mon Sep 17 00:00:00 2001 From: linxea Date: Tue, 1 Mar 2016 15:14:32 +0800 Subject: [PATCH 47/55] Update upload path in videoController.js --- api/controllers/VideoController.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js index 7b5c094..80d132a 100644 --- a/api/controllers/VideoController.js +++ b/api/controllers/VideoController.js @@ -6,7 +6,7 @@ */ module.exports = { - + /** * `VideoController.create()` * Usage: POST /api/video @@ -72,7 +72,7 @@ module.exports = { /** * `VideoController.tags()` - * Usage: + * Usage: */ tags: function (req, res) { return res.json({ @@ -104,8 +104,8 @@ module.exports = { dirname: sails.config.appPath + '/.tmp/public/upload/vid/' + req.param('id'), maxBytes: 2 * 1000 * 1000 * 1000 }, function (err, uploadedFiles) { - if (err) return res.negotiate(err); - + if (err) return res.negotiate(err); + // If no files were uploaded, respond with an error. if (uploadedFiles.length === 0){ return res.badRequest('No file was uploaded'); @@ -125,8 +125,6 @@ module.exports = { video: updatedVideo[0] }); }); - }); } -}; - +}; \ No newline at end of file From 99a2765b7500cab633c283221ffb3c4c2c410633 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 15:54:38 +0800 Subject: [PATCH 48/55] Update delete button style in dashboard page --- assets/styles/custom/dashboard.less | 8 ++++++++ views/dashboard.ejs | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/assets/styles/custom/dashboard.less b/assets/styles/custom/dashboard.less index da7d506..a97e73d 100644 --- a/assets/styles/custom/dashboard.less +++ b/assets/styles/custom/dashboard.less @@ -28,6 +28,14 @@ margin: 0 10px 0 25px; } + .md-icon-button.delete-btn { + margin-top: 5px; + + &:hover { + background-color: @color-grey-300; + } + } + .md-no-style.md-list-item-inner { padding: 15px 0; } diff --git a/views/dashboard.ejs b/views/dashboard.ejs index 1a4873f..0c08252 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -86,7 +86,9 @@
Edit Video - + + +
@@ -100,4 +102,4 @@
-
\ No newline at end of file +
From 9e72ade585eee4fddea241184b216fd27ae0195a Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 16:01:08 +0800 Subject: [PATCH 49/55] Fix privacy icon showing the opposite value in dashboard --- views/dashboard.ejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/dashboard.ejs b/views/dashboard.ejs index 0c08252..7cde8f9 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -72,7 +72,7 @@ {{ video.views }} Views · {{ video.createdAt | date:'medium'}} - + From cfb08d6ab7c900e7fff776b504168bb3bb9f752d Mon Sep 17 00:00:00 2001 From: Nelson Goh Wei Qiang Date: Tue, 1 Mar 2016 16:47:25 +0800 Subject: [PATCH 50/55] reduce the instructions of the README, abstract the installation instructions to the relevant packages --- README.md | 57 ++++++++++++------------------------------------------- 1 file changed, 12 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index ee4ffa8..588a592 100644 --- a/README.md +++ b/README.md @@ -9,52 +9,19 @@ Pull a copy of this project from GitHub (link above) or download the project as ### Step 2: Installing the necessary pre-requisites -Note: Some of these commands may require permissions to install the relevant packages. As such adding 'sudo' before the actual command (e.g. npm) may help. +#### The following packages are required for using zoomable.js: +[Node.js](http://nodejs.org) - For package installation +[Grunt](http://gruntjs.com/) - For generating front-end assets +[Bower](http://www.npmjs.com/package/bower) - For package installation +[Sails.js](http://sailsjs.org/get-started) - For server creation +[FFMPEG](https://ffmpeg.org/download.html) - For video conversion, thumnbnail generation +[MP4Box](https://gpac.wp.mines-telecom.fr/downloads/) - For MPD creation for the videos -#### Install [Node.js](http://nodejs.org) on your machine +### Step 3: Install additional dependencies and libraries -If you have Homebrew installed on your machine (e.g. Mac OSX): -```bash -$ brew install node -``` -OR - -If the MSI installer option is available for your OS: -Download the MSI installer from the Node.js website and run the installer - -#### Redirecting the directory - -Change the current working directory from the command-line interface to that of the project folder - -For example (Terminal on Mac OSX): -```bash -$ cd Desktop\zoomable.js -``` - -#### Install [Grunt](http://gruntjs.com/) - -Run the Node Package Manager (npm) installation command for the Grunt package: -```bash -$ npm install -g grunt-cli -``` - -#### Install [Bower](http://www.npmjs.com/package/bower) - -and the Bower package: -```bash -$ npm install -g bower -``` - -#### Install [Sails.js](http://sailsjs.org/get-started) - -as well as the Sails.js package: -```bash -$ npm install -g sails -``` - -### Step 3: Install the other dependencies and libraries +Run the npm and bower install commands to install the dependencies and libraries that the project requires. These dependencies and libraries are indicated inside package.json (for npm install) and bower.json (for bower install). -Running the npm and bower install commands will install the dependencies and libraries that the project requires. These dependencies and libraries are indicated inside package.json (for npm install) and bower.json (for bower install). +For bash: ```bash $ npm install $ bower install @@ -62,7 +29,7 @@ $ bower install ### Step 4: Generate latest front-end assets -Running the Grunt command will update the changes made to the front-end assets and compile it for further use later. +Run the Grunt command to update the changes made to the front-end assets and compile it for further use later. ```bash $ grunt ``` @@ -74,7 +41,7 @@ Start the server by running the Sails command: $ sails lift ``` -If this server is setup locally, open your browser and enter the url: http://localhost:1337 to visit the main webpage of the project. +If this server is setup locally, open your browser and enter the url: [http://localhost:1337](http://localhost:1337) to visit the main webpage of the project. ## Usage `to be updated` From 36967a8e11ef2881253112a6c4d67fdeeeffbabc Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 1 Mar 2016 17:01:22 +0800 Subject: [PATCH 51/55] Change videoDir to mpdDir --- api/models/Video.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/api/models/Video.js b/api/models/Video.js index 347d1eb..7d720e2 100644 --- a/api/models/Video.js +++ b/api/models/Video.js @@ -44,14 +44,12 @@ module.exports = { defaultsTo: 0 }, - videoDir: { - type: 'string', - required: true + mpdDir: { + type: 'string' }, thumbnailDir: { - type: 'string', - required: true + type: 'string' }, createdAt: { From 66ee8bd9c1d74fdac4ce6c0282fad19ea0b8e7d0 Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 1 Mar 2016 17:03:19 +0800 Subject: [PATCH 52/55] Update the path to save uploaded video --- api/controllers/VideoController.js | 1 + 1 file changed, 1 insertion(+) diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js index 1ddb44f..36a9c7b 100644 --- a/api/controllers/VideoController.js +++ b/api/controllers/VideoController.js @@ -36,6 +36,7 @@ module.exports = { * Usage: GET /api/video */ readAll: function (req, res) { + console.log(sails.getBaseUrl()); Video.find().exec(function (err, videos) { if (err) throw err; res.json(videos); From 0c0666df1143e6938675fe11108a498eeac1b3b7 Mon Sep 17 00:00:00 2001 From: jiale Date: Tue, 1 Mar 2016 17:03:47 +0800 Subject: [PATCH 53/55] Remove VideoProcessing Service and combine with ProcessingController --- api/controllers/ProcessingController.js | 34 +++++++++++++++++--- api/services/VideoProcessingService.js | 41 ------------------------- 2 files changed, 30 insertions(+), 45 deletions(-) delete mode 100644 api/services/VideoProcessingService.js diff --git a/api/controllers/ProcessingController.js b/api/controllers/ProcessingController.js index 1fc850d..21bddfe 100644 --- a/api/controllers/ProcessingController.js +++ b/api/controllers/ProcessingController.js @@ -5,15 +5,41 @@ * @help :: See http://links.sailsjs.org/docs/controllers */ +var spawn = require('child_process').spawn; + module.exports = { /** - * Usage: POST /api/processing/run - * Content: {dir: ':dir'} + * Usage: POST /api/processing/run + * Content: {dir: ':dir', filename: ':filename'} **/ run: function (req, res) { - console.log(req.param('dir')); - VideoProcessingService.automateProcessing({dir: req.param('dir')}); + var dirPath = req.param('dir'); + var fileName = sails.getBaseUrl() + '/' + req.param('filename'); + + var command = spawn(sails.config.appPath + '/scripts/video-processing.sh', [dirPath]); + + command.stdout.on('data', function(chunk) { + console.log('stdout: ' + chunk); + }); + + command.stderr.on('data', function(chunk) { + console.log('stderr: ' + chunk); + // res.send(500); // when script fails, generate a Server Error HTTP res + }); + + command.on('close', function(chunk) { + // return json contains mpdDir and thumbnailDir + res.json({ + mpdDir: [ + filename+'_mpd_R1C1.mpd', filename+'_mpd_R1C2.mpd', filename+'_mpd_R1C3.mpd', filename+'_mpd_R1C4.mpd', + filename+'_mpd_R2C1.mpd', filename+'_mpd_R2C2.mpd', filename+'_mpd_R2C3.mpd', filename+'_mpd_R2C4.mpd', + filename+'_mpd_R3C1.mpd', filename+'_mpd_R3C2.mpd', filename+'_mpd_R3C3.mpd', filename+'_mpd_R3C4.mpd', + filename+'_mpd_R4C1.mpd', filename+'_mpd_R4C2.mpd', filename+'_mpd_R4C3.mpd', filename+'_mpd_R4C4.mpd' + ], + thumbnailDir: filename + '.png' + }); + }); } }; diff --git a/api/services/VideoProcessingService.js b/api/services/VideoProcessingService.js deleted file mode 100644 index c953209..0000000 --- a/api/services/VideoProcessingService.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * VideoProcessingService - * - * @description :: Server-side logic to automate video processing - * @help :: See http://links.sailsjs.org/docs/service - */ - -// var fs = require('fs'); -var spawn = require('child_process').spawn; - -module.exports = { - - automateProcessing: function(options) { - // To get dir, use options.dir - dirPath = options.dir; - // var videoFile = fs.readdir(dirPath, function(error, listOfVids) { - // // If an error has occurred - // if (error) { - // // Print the error for now, throw an error for a error handler later - // console.log(error) - // } - // }) - - var command = spawn(sails.config.appPath + '/scripts/video-processing.sh', [dirPath]); - - command.stdout.on('data', function(chunk) { - console.log('stdout: ' + chunk); - }); - - command.stderr.on('data', function(chunk) { - console.log('stderr: ' + chunk); - res.send(500); // when script fails, generate a Server Error HTTP res - }); - - command.on('close', function(chunk) { - res.send("Complete processing the uploaded video!"); - }); - - // console.log(videoFile[0]); - } -}; \ No newline at end of file From f9d871142909eba37e54e1fe3a5824e97d032b93 Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 17:38:37 +0800 Subject: [PATCH 54/55] Made search bar only show in dashboard page --- assets/js/public/controllers/loginController.js | 10 ++++++++++ views/navbar.ejs | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/assets/js/public/controllers/loginController.js b/assets/js/public/controllers/loginController.js index 25ed33d..2c2c834 100644 --- a/assets/js/public/controllers/loginController.js +++ b/assets/js/public/controllers/loginController.js @@ -6,6 +6,16 @@ angular.module('zoomableApp').controller('loginController', function($scope, $st $scope.isCreate = false; $scope.errorMsg = ''; + // FUNCTIONS FOR NAVBAR + $scope.isVideoList = function() { + if (location.pathname == '/') { + return true; + } + else { + return false; + } + } + // FUNCTIONS FOR LOGIN FORM $scope.submitForm = function() { if ($scope.isCreate) { diff --git a/views/navbar.ejs b/views/navbar.ejs index 52f383c..d833cea 100644 --- a/views/navbar.ejs +++ b/views/navbar.ejs @@ -12,7 +12,7 @@ - + From d8fea51d7373a1558c0ddd3a85ea899e9205745e Mon Sep 17 00:00:00 2001 From: kphua Date: Tue, 1 Mar 2016 17:41:34 +0800 Subject: [PATCH 55/55] Search functionality in dashboard page --- .../js/public/controllers/dashboardController.js | 7 ++++++- assets/js/public/controllers/loginController.js | 8 ++++++++ assets/js/public/zoomableApp.js | 14 ++++++++++---- views/navbar.ejs | 2 +- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/assets/js/public/controllers/dashboardController.js b/assets/js/public/controllers/dashboardController.js index cb2174a..de6fd86 100644 --- a/assets/js/public/controllers/dashboardController.js +++ b/assets/js/public/controllers/dashboardController.js @@ -181,4 +181,9 @@ angular.module('zoomableApp').controller('dashboardController', function($scope, }); } } -}); \ No newline at end of file + + /* Function to catch broadcast event from login controller to perform search on video list */ + $scope.$on('searchBroadcast', function(event, query) { + $scope.filterType = query; + }); +}); diff --git a/assets/js/public/controllers/loginController.js b/assets/js/public/controllers/loginController.js index 2c2c834..61a1994 100644 --- a/assets/js/public/controllers/loginController.js +++ b/assets/js/public/controllers/loginController.js @@ -5,8 +5,11 @@ angular.module('zoomableApp').controller('loginController', function($scope, $st $scope.emailAddress = ''; $scope.isCreate = false; $scope.errorMsg = ''; + $scope.searchQuery = ''; // FUNCTIONS FOR NAVBAR + + /* Function to control showing of search input in navbar */ $scope.isVideoList = function() { if (location.pathname == '/') { return true; @@ -16,6 +19,11 @@ angular.module('zoomableApp').controller('loginController', function($scope, $st } } + /* Function to call search in dashboardController */ + $scope.searchVideoList = function() { + $scope.$emit('searchEmit', $scope.searchQuery); + } + // FUNCTIONS FOR LOGIN FORM $scope.submitForm = function() { if ($scope.isCreate) { diff --git a/assets/js/public/zoomableApp.js b/assets/js/public/zoomableApp.js index 44dc222..d17757e 100644 --- a/assets/js/public/zoomableApp.js +++ b/assets/js/public/zoomableApp.js @@ -2,8 +2,14 @@ angular.module('zoomableApp', ['ui.router', 'ngMaterial', 'ngMessages', 'ngclipb // Define standard theme for dashboard UI .config(function($mdThemingProvider) { $mdThemingProvider.theme('default') - .primaryPalette('cyan', { - 'default': '700' // use shade 700 for default, and keep all other shades the same - }) - .accentPalette('red'); + .primaryPalette('cyan', { + 'default': '700' // use shade 700 for default, and keep all other shades the same + }) + .accentPalette('red'); +}) +.run(function ($rootScope) { + // control searching in dashboard page (call from loginController to dashboardController) + $rootScope.$on('searchEmit', function(event, args) { + $rootScope.$broadcast('searchBroadcast', args); + }); }); diff --git a/views/navbar.ejs b/views/navbar.ejs index d833cea..0e5a760 100644 --- a/views/navbar.ejs +++ b/views/navbar.ejs @@ -14,7 +14,7 @@ - +