├── public ├── img │ └── logo.png ├── lib │ ├── videojs │ │ ├── font │ │ │ ├── vjs.eot │ │ │ ├── vjs.ttf │ │ │ ├── vjs.woff │ │ │ └── vjs.svg │ │ ├── video-js.swf │ │ ├── demo.captions.vtt │ │ ├── demo.html │ │ ├── video-js.min.css │ │ └── video-js.css │ ├── bootstrap │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ └── glyphicons-halflings-regular.woff │ │ ├── css │ │ │ ├── bootstrap-theme.min.css │ │ │ └── bootstrap-theme.css │ │ └── js │ │ │ └── bootstrap.min.js │ └── resumable.js ├── css │ ├── video.less │ ├── video.css │ ├── style.less │ └── style.css └── js │ ├── index.coffee │ ├── index.js │ ├── video.coffee │ ├── add_video.coffee │ ├── video.js │ └── add_video.js ├── uploads ├── thumb │ ├── thumb_default.png │ └── index.html └── index.html ├── temp └── index.html ├── config.default.coffee ├── config.default.js ├── .gitignore ├── package.json ├── modules ├── db.coffee ├── db.js ├── util.coffee ├── sockets.coffee ├── sockets.js ├── util.js └── resumable-node.js ├── README.md ├── global.coffee ├── views ├── index.twig ├── video_list.twig ├── layout.twig ├── video.twig └── add_video.twig ├── global.js ├── routes ├── index.coffee ├── index.js ├── video.coffee └── video.js ├── app.js └── model ├── Video.coffee └── Video.js /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/img/logo.png -------------------------------------------------------------------------------- /public/lib/videojs/font/vjs.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/lib/videojs/font/vjs.eot -------------------------------------------------------------------------------- /public/lib/videojs/font/vjs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/lib/videojs/font/vjs.ttf -------------------------------------------------------------------------------- /public/lib/videojs/video-js.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/lib/videojs/video-js.swf -------------------------------------------------------------------------------- /uploads/thumb/thumb_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/uploads/thumb/thumb_default.png -------------------------------------------------------------------------------- /public/lib/videojs/font/vjs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/lib/videojs/font/vjs.woff -------------------------------------------------------------------------------- /temp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /config.default.coffee: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | db: 'dbname', 3 | host: 'dbhost', 4 | ffmpegPath: 'path to ffmpeg.exe', 5 | cookieSecret: 'secr', 6 | } -------------------------------------------------------------------------------- /uploads/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/lib/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /uploads/thumb/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Allenice/danmu/HEAD/public/lib/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /config.default.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | module.exports = { 4 | db: 'dbname', 5 | host: 'dbhost', 6 | ffmpegPath: 'path to ffmpeg.exe', 7 | cookieSecret: 'secr' 8 | }; 9 | 10 | }).call(this); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.mp4 10 | *.flv 11 | .idea 12 | settings.coffee 13 | settings.js 14 | config.coffee 15 | config.js 16 | pids 17 | logs 18 | results 19 | test 20 | uploads/thumb/*.jpg 21 | 22 | 23 | npm-debug.log 24 | node_modules 25 | -------------------------------------------------------------------------------- /public/css/video.less: -------------------------------------------------------------------------------- 1 | @topIndex: 2147483648; 2 | 3 | html {height: 100%;} 4 | #video {position:relative;z-index: 100;width:600px;height:400px;float: left;} 5 | .test {width: 100px; height: 100px; background-color: red; color: #FFF; position: fixed; left: 50%; top:50%; margin-top: -50px; margin-left: -50px; z-index: @topIndex;} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "application-name", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "3.4.7", 10 | "twig": ">= 0.6.0", 11 | "socket.io": ">= 0.9.16", 12 | "mongodb": ">= 0.9.9", 13 | "fluent-ffmpeg": "~1.5.2", 14 | "escape-html": "*", 15 | "moment": "*" 16 | } 17 | } -------------------------------------------------------------------------------- /public/css/video.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | #video { 5 | position: relative; 6 | z-index: 100; 7 | width: 600px; 8 | height: 400px; 9 | float: left; 10 | } 11 | .test { 12 | width: 100px; 13 | height: 100px; 14 | background-color: red; 15 | color: #FFF; 16 | position: fixed; 17 | left: 50%; 18 | top: 50%; 19 | margin-top: -50px; 20 | margin-left: -50px; 21 | z-index: 2147483648; 22 | } 23 | -------------------------------------------------------------------------------- /modules/db.coffee: -------------------------------------------------------------------------------- 1 | settings = require '../config' 2 | mongo = require('mongodb'); 3 | 4 | mongoDb = new mongo.Db(settings.db, new mongo.Server(settings.host, mongo.Connection.DEFAULT_PORT, {})) 5 | 6 | mongoDb.open (err, db) -> 7 | if err 8 | console.log err 9 | return 10 | 11 | if global.db 12 | global.db.close() 13 | console.log 'Database disconnected!' 14 | 15 | global.db = db 16 | console.log 'Database connected!' 17 | 18 | module.exports = mongoDb -------------------------------------------------------------------------------- /modules/db.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var mongo, mongoDb, settings; 4 | 5 | settings = require('../config'); 6 | 7 | mongo = require('mongodb'); 8 | 9 | mongoDb = new mongo.Db(settings.db, new mongo.Server(settings.host, mongo.Connection.DEFAULT_PORT, {})); 10 | 11 | mongoDb.open(function(err, db) { 12 | if (err) { 13 | console.log(err); 14 | return; 15 | } 16 | if (global.db) { 17 | global.db.close(); 18 | console.log('Database disconnected!'); 19 | } 20 | global.db = db; 21 | return console.log('Database connected!'); 22 | }); 23 | 24 | module.exports = mongoDb; 25 | 26 | }).call(this); 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 弹幕网站示例 2 | === 3 | 基于nodejs+socket.io+mongodb的弹幕视频网站示例 4 | 5 | 安装 6 | === 7 | - 请先安装以下软件,并配置好环境变量 8 | - mongodb 9 | - ffmpeg 10 | 11 | - 设置ffmpeg的执行路径和数据库 12 | ```coffeescript 13 | # config.coffee 14 | module.exports = { 15 | cookieSecret: 'socket.io', 16 | db: 'danmu', 17 | host: 'localhost', 18 | ffmpegPath: 'E:\\ffmpeg\\bin\\ffmpeg.exe' 19 | } 20 | ``` 21 | - 请把ffmpeg放到项目的同一个磁盘分区 22 | 23 | - 安装依赖库 24 | ```coffeescript 25 | # 在项目的根目录下执行 26 | npm install 27 | ``` 28 | 29 | 运行 30 | === 31 | - 启动app 32 | ``` 33 | # shell or cmd 34 | node app.js 35 | ``` 36 | - 先访问以下地址安装数据库 37 | ``` 38 | # 39 | http://localhost:3000/install 40 | ``` 41 | - 访问 42 | ``` 43 | # 44 | http://localhost:3000/ 45 | ``` 46 | -------------------------------------------------------------------------------- /global.coffee: -------------------------------------------------------------------------------- 1 | # global var 2 | 3 | path = require 'path' 4 | 5 | # auto increace id, get the next id 6 | global.getNextId = (name, callback) -> 7 | global.db.collection('counters').findAndModify {_id: name}, [['_id', 'asc']], { $inc: { seq: 1 } }, {"new": true}, callback 8 | 9 | 10 | # gloabl resumable obj 11 | global.resumable = require('./modules/resumable-node.js')(path.join(__dirname, '/temp/')); 12 | 13 | # 14 | global.STATUS_SUCCESS = 'success' 15 | global.STATUS_ERROR = 'error' 16 | 17 | global.json_response = (res, status, data, message, page) -> 18 | res.set 'Content-Type', 'application/json' 19 | 20 | json = JSON.stringify({ 21 | status: status, 22 | data: data, 23 | message: message, 24 | page: page 25 | }) 26 | 27 | res.end json 28 | 29 | module.exports = global -------------------------------------------------------------------------------- /views/index.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 7 | 8 | 9 | 10 | 11 |
12 |

当前人数: 0

13 |
14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /views/video_list.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.twig' %} 2 | 3 | {% block content %} 4 | 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /public/js/index.coffee: -------------------------------------------------------------------------------- 1 | # index.js 2 | $ ()-> 3 | 4 | getUrlValue = (name) -> 5 | return decodeURI((RegExp(name + '=' + '(.+?)(&|$)').exec(location.search)||['',null])[1]) 6 | 7 | vid = getUrlValue('id') || 0 8 | 9 | socket = io.connect('/video') 10 | 11 | socket.on 'connected', () -> 12 | socket.emit 'connected', {id: vid} 13 | 14 | socket.on 'new message'+vid, (msg) -> 15 | $item = $ "
#{msg}
" 16 | $('#container .messages').append $item 17 | return 18 | 19 | $("#container input:text").keyup (e)-> 20 | if e.keyCode == 13 21 | socket.emit 'send'+vid, $(this).val() 22 | $(this).val('') 23 | return 24 | 25 | socket.on 'someone connect'+vid, (data)-> 26 | $('#count').text data.count 27 | 28 | window.onbeforeunload = (e) -> 29 | socket.emit 'someone disconnected'+vid, {id: vid} 30 | return 31 | 32 | return -------------------------------------------------------------------------------- /public/lib/videojs/demo.captions.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 00:00.700 --> 00:04.110 4 | Captions describe all relevant audio for the hearing impaired. 5 | [ Heroic music playing for a seagull ] 6 | 7 | 00:04.500 --> 00:05.000 8 | [ Splash!!! ] 9 | 10 | 00:05.100 --> 00:06.000 11 | [ Sploosh!!! ] 12 | 13 | 00:08.000 --> 00:09.225 14 | [ Splash...splash...splash splash splash ] 15 | 16 | 00:10.525 --> 00:11.255 17 | [ Splash, Sploosh again ] 18 | 19 | 00:13.500 --> 00:14.984 20 | Dolphin: eeeEEEEEeeee! 21 | 22 | 00:14.984 --> 00:16.984 23 | Dolphin: Squawk! eeeEEE? 24 | 25 | 00:25.000 --> 00:28.284 26 | [ A whole ton of splashes ] 27 | 28 | 00:29.500 --> 00:31.000 29 | Mine. Mine. Mine. 30 | 31 | 00:34.300 --> 00:36.000 32 | Shark: Chomp 33 | 34 | 00:36.800 --> 00:37.900 35 | Shark: CHOMP!!! 36 | 37 | 00:37.861 --> 00:41.193 38 | EEEEEEOOOOOOOOOOWHALENOISE 39 | 40 | 00:42.593 --> 00:45.611 41 | [ BIG SPLASH ] -------------------------------------------------------------------------------- /global.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var path; 4 | 5 | path = require('path'); 6 | 7 | global.getNextId = function(name, callback) { 8 | return global.db.collection('counters').findAndModify({ 9 | _id: name 10 | }, [['_id', 'asc']], { 11 | $inc: { 12 | seq: 1 13 | } 14 | }, { 15 | "new": true 16 | }, callback); 17 | }; 18 | 19 | global.resumable = require('./modules/resumable-node.js')(path.join(__dirname, '/temp/')); 20 | 21 | global.STATUS_SUCCESS = 'success'; 22 | 23 | global.STATUS_ERROR = 'error'; 24 | 25 | global.json_response = function(res, status, data, message, page) { 26 | var json; 27 | res.set('Content-Type', 'application/json'); 28 | json = JSON.stringify({ 29 | status: status, 30 | data: data, 31 | message: message, 32 | page: page 33 | }); 34 | return res.end(json); 35 | }; 36 | 37 | module.exports = global; 38 | 39 | }).call(this); 40 | -------------------------------------------------------------------------------- /views/layout.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ title }} 6 | 7 | 8 | 9 | {% block css %} 10 | {% endblock %} 11 | 12 | 13 | 19 |
20 | {% block content %} 21 | {% endblock %} 22 |
23 | 24 | 25 | 26 | {% block footer %} 27 | {% endblock %} 28 | 29 | -------------------------------------------------------------------------------- /routes/index.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | module.exports = (app) -> 4 | 5 | video_route = require './video' 6 | video_route(app) 7 | 8 | # index 9 | app.get '/', (req, res) -> 10 | res.redirect '/video' 11 | 12 | app.get '/404.html', (req, res) -> 13 | res.end '404 error' 14 | 15 | # install-- init the counter 16 | app.get '/install', (req, res) -> 17 | 18 | if !global.db 19 | res.end 'install failed, Database is not connected!' 20 | return 21 | 22 | global.db.collection('counters').insert [ 23 | { 24 | _id: 'video_id', 25 | seq: 1000 26 | }, 27 | { 28 | _id: 'barrage_id', 29 | seq: 0 30 | } 31 | ], (err, item) -> 32 | if err 33 | res.end err.message 34 | else 35 | res.end 'install successfully!' 36 | 37 | app.get '/resources/:filename', (req, res) -> 38 | path = require 'path' 39 | res.sendfile path.join __dirname, '../uploads/'+req.params.filename 40 | 41 | 42 | # upload 43 | app.post '/upload', (req, res) -> 44 | global.resumable.post req, (status, filename, original_filename, identifier) -> 45 | res.send status -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | $(function() { 4 | var getUrlValue, socket, vid; 5 | getUrlValue = function(name) { 6 | return decodeURI((RegExp(name + '=' + '(.+?)(&|$)').exec(location.search) || ['', null])[1]); 7 | }; 8 | vid = getUrlValue('id') || 0; 9 | socket = io.connect('/video'); 10 | socket.on('connected', function() { 11 | return socket.emit('connected', { 12 | id: vid 13 | }); 14 | }); 15 | socket.on('new message' + vid, function(msg) { 16 | var $item; 17 | $item = $("
" + msg + "
"); 18 | $('#container .messages').append($item); 19 | }); 20 | $("#container input:text").keyup(function(e) { 21 | if (e.keyCode === 13) { 22 | socket.emit('send' + vid, $(this).val()); 23 | $(this).val(''); 24 | } 25 | }); 26 | socket.on('someone connect' + vid, function(data) { 27 | return $('#count').text(data.count); 28 | }); 29 | window.onbeforeunload = function(e) { 30 | socket.emit('someone disconnected' + vid, { 31 | id: vid 32 | }); 33 | }; 34 | }); 35 | 36 | }).call(this); 37 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var http = require('http'); 7 | var path = require('path'); 8 | var express = require('express'); 9 | var routes = require('./routes'); 10 | 11 | var sockets = require('./modules/sockets'); 12 | var mongoDb = require('./modules/db'); 13 | var global = require('./global'); 14 | 15 | global.resumable.uploadDir = path.join(__dirname, '/uploads') 16 | 17 | var app = express(); 18 | 19 | // all environments 20 | app.set('port', process.env.PORT || 3000); 21 | app.set('views', path.join(__dirname, 'views')); 22 | app.set('view engine', 'twig'); 23 | app.use(express.favicon()); 24 | app.use(express.logger('dev')); 25 | 26 | app.use(express.bodyParser({ 27 | keepExtensions: true, 28 | uploadDir: './temp' 29 | })); 30 | 31 | app.use(express.methodOverride()); 32 | app.use(app.router); 33 | app.use(express.static(path.join(__dirname, 'public'))); 34 | app.use(express.static(path.join(__dirname, 'public/lib/'))); 35 | 36 | // development only 37 | if ('development' == app.get('env')) { 38 | app.use(express.errorHandler()); 39 | } 40 | 41 | routes(app); 42 | 43 | var server = http.createServer(app).listen(app.get('port'), function(){ 44 | console.log('Express server listening on port ' + app.get('port')); 45 | }); 46 | 47 | var io = require('socket.io').listen(server); 48 | sockets(io); -------------------------------------------------------------------------------- /public/lib/videojs/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Video.js | HTML5 Video Player 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | module.exports = function(app) { 4 | var video_route; 5 | video_route = require('./video'); 6 | video_route(app); 7 | app.get('/', function(req, res) { 8 | return res.redirect('/video'); 9 | }); 10 | app.get('/404.html', function(req, res) { 11 | return res.end('404 error'); 12 | }); 13 | app.get('/install', function(req, res) { 14 | if (!global.db) { 15 | res.end('install failed, Database is not connected!'); 16 | return; 17 | } 18 | return global.db.collection('counters').insert([ 19 | { 20 | _id: 'video_id', 21 | seq: 1000 22 | }, { 23 | _id: 'barrage_id', 24 | seq: 0 25 | } 26 | ], function(err, item) { 27 | if (err) { 28 | return res.end(err.message); 29 | } else { 30 | return res.end('install successfully!'); 31 | } 32 | }); 33 | }); 34 | app.get('/resources/:filename', function(req, res) { 35 | var path; 36 | path = require('path'); 37 | return res.sendfile(path.join(__dirname, '../uploads/' + req.params.filename)); 38 | }); 39 | return app.post('/upload', function(req, res) { 40 | return global.resumable.post(req, function(status, filename, original_filename, identifier) { 41 | return res.send(status); 42 | }); 43 | }); 44 | }; 45 | 46 | }).call(this); 47 | -------------------------------------------------------------------------------- /modules/util.coffee: -------------------------------------------------------------------------------- 1 | 2 | moment = require 'moment' 3 | 4 | exports.mergeFile = (filename, identifier, callback) -> 5 | fs = require 'fs' 6 | path = require 'path' 7 | 8 | time = (new Date()).getTime() 9 | format = filename.substr(filename.lastIndexOf('.')+1) 10 | saveName = time + '.' + format 11 | saveFullName = path.join(global.resumable.uploadDir, saveName) 12 | 13 | fs.writeFile saveFullName,'', {flag: 'w+'}, (err) -> 14 | if err 15 | callback err 16 | return 17 | 18 | mergeFile = (chunkNum) -> 19 | chunkFileName = path.join global.resumable.temporaryFolder, '/resumable-'+identifier+'.'+chunkNum 20 | fs.exists chunkFileName, (exists) -> 21 | if exists 22 | fs.readFile chunkFileName, (err, data) -> 23 | if err 24 | callback err 25 | return 26 | 27 | fs.appendFile saveFullName, data, (err) -> 28 | if err 29 | callback err 30 | return 31 | fs.unlink chunkFileName, (err) -> 32 | mergeFile(++chunkNum) 33 | else 34 | callback null, saveName, format, time 35 | console.log 'file: ' + chunkFileName + ' not exists' 36 | return 37 | return 38 | 39 | mergeFile(1) 40 | 41 | exports.getFormatDate = (time) -> 42 | date = moment(parseFloat(time)) 43 | second = date.get('second') 44 | if second < 10 45 | second = '0' + second 46 | return date.get('year')+'-'+(date.get('month')+1)+'-'+date.get('date')+' '+date.get('hour')+':'+date.get('minute')+':'+second -------------------------------------------------------------------------------- /modules/sockets.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (io) -> 2 | 3 | escape = require 'escape-html' 4 | VideoModel = require '../model/Video' 5 | 6 | connectCount = { 7 | 'i0': 0 8 | } 9 | 10 | global.video_sockets = video_sockets = io.of('/video') 11 | 12 | video_sockets.on 'connection', (socket) -> 13 | connectCount['i0']++ 14 | 15 | # server send connected info to client 16 | socket.emit 'connected' 17 | 18 | # server get info from client after connected 19 | socket.on 'connected', (data) -> 20 | vid = data.id 21 | vindex = 'i'+vid 22 | 23 | socket.v_data = data 24 | 25 | if !connectCount[vindex] 26 | connectCount[vindex] = 0 27 | connectCount[vindex]++ 28 | 29 | # Tell the client that server has connected and pass the connection count of current video to client. 30 | video_sockets.emit 'connected'+vid, {count: connectCount[vindex]} 31 | 32 | # 弹幕 33 | socket.on 'barrage'+vid, (barrage)-> 34 | barrage.content = escape barrage.content 35 | barrage.time = (new Date()).getTime() 36 | VideoModel.addBarrage vid, barrage, (err) -> 37 | video_sockets.emit 'barrage'+vid, barrage 38 | 39 | # 全站总连接数 40 | video_sockets.emit 'total connection', {count: connectCount['i0']} 41 | 42 | # 有人失去连接 43 | socket.on 'disconnect', ()-> 44 | vindex = 'i' + socket.v_data.id 45 | 46 | connectCount['i0']-- 47 | video_sockets.emit 'someone connect', {count: connectCount['i0']} 48 | 49 | connectCount[vindex]-- 50 | video_sockets.emit 'connected' + socket.v_data.id, {count: connectCount[vindex]} 51 | 52 | socket.emit '' 53 | return 54 | 55 | return 56 | 57 | 58 | return -------------------------------------------------------------------------------- /modules/sockets.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | (function() { 3 | module.exports = function(io) { 4 | var VideoModel, connectCount, escape, video_sockets; 5 | escape = require('escape-html'); 6 | VideoModel = require('../model/Video'); 7 | connectCount = { 8 | 'i0': 0 9 | }; 10 | global.video_sockets = video_sockets = io.of('/video'); 11 | video_sockets.on('connection', function(socket) { 12 | connectCount['i0']++; 13 | socket.emit('connected'); 14 | socket.on('connected', function(data) { 15 | var vid, vindex; 16 | vid = data.id; 17 | vindex = 'i' + vid; 18 | socket.v_data = data; 19 | if (!connectCount[vindex]) { 20 | connectCount[vindex] = 0; 21 | } 22 | connectCount[vindex]++; 23 | video_sockets.emit('connected' + vid, { 24 | count: connectCount[vindex] 25 | }); 26 | return socket.on('barrage' + vid, function(barrage) { 27 | barrage.content = escape(barrage.content); 28 | barrage.time = (new Date()).getTime(); 29 | return VideoModel.addBarrage(vid, barrage, function(err) { 30 | return video_sockets.emit('barrage' + vid, barrage); 31 | }); 32 | }); 33 | }); 34 | video_sockets.emit('total connection', { 35 | count: connectCount['i0'] 36 | }); 37 | socket.on('disconnect', function() { 38 | var vindex; 39 | vindex = 'i' + socket.v_data.id; 40 | connectCount['i0']--; 41 | video_sockets.emit('someone connect', { 42 | count: connectCount['i0'] 43 | }); 44 | connectCount[vindex]--; 45 | video_sockets.emit('connected' + socket.v_data.id, { 46 | count: connectCount[vindex] 47 | }); 48 | socket.emit(''); 49 | }); 50 | }); 51 | }; 52 | 53 | }).call(this); 54 | 55 | //# sourceMappingURL=sockets.map 56 | -------------------------------------------------------------------------------- /views/video.twig: -------------------------------------------------------------------------------- 1 | 2 | {% extends 'layout.twig' %} 3 | 4 | {% block css %} 5 | 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
10 |
11 |
12 | 15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |
25 | 当前人数: 0 26 | 评论数: {{ video.barrage_count }} 27 |
28 |
    29 | {% for barrage in video.barrage %} 30 |
  • {{ barrage.content }}
  • 34 | {% endfor %} 35 |
36 |
37 |
38 |
39 | {% endblock %} 40 | 41 | {% block footer %} 42 | 43 | 44 | 45 | 46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /modules/util.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var moment; 4 | 5 | moment = require('moment'); 6 | 7 | exports.mergeFile = function(filename, identifier, callback) { 8 | var format, fs, path, saveFullName, saveName, time; 9 | fs = require('fs'); 10 | path = require('path'); 11 | time = (new Date()).getTime(); 12 | format = filename.substr(filename.lastIndexOf('.') + 1); 13 | saveName = time + '.' + format; 14 | saveFullName = path.join(global.resumable.uploadDir, saveName); 15 | return fs.writeFile(saveFullName, '', { 16 | flag: 'w+' 17 | }, function(err) { 18 | var mergeFile; 19 | if (err) { 20 | callback(err); 21 | return; 22 | } 23 | mergeFile = function(chunkNum) { 24 | var chunkFileName; 25 | chunkFileName = path.join(global.resumable.temporaryFolder, '/resumable-' + identifier + '.' + chunkNum); 26 | fs.exists(chunkFileName, function(exists) { 27 | if (exists) { 28 | fs.readFile(chunkFileName, function(err, data) { 29 | if (err) { 30 | callback(err); 31 | return; 32 | } 33 | return fs.appendFile(saveFullName, data, function(err) { 34 | if (err) { 35 | callback(err); 36 | return; 37 | } 38 | fs.unlink(chunkFileName, function(err) {}); 39 | return mergeFile(++chunkNum); 40 | }); 41 | }); 42 | } else { 43 | callback(null, saveName, format, time); 44 | console.log('file: ' + chunkFileName + ' not exists'); 45 | } 46 | }); 47 | }; 48 | return mergeFile(1); 49 | }); 50 | }; 51 | 52 | exports.getFormatDate = function(time) { 53 | var date, second; 54 | date = moment(parseFloat(time)); 55 | second = date.get('second'); 56 | if (second < 10) { 57 | second = '0' + second; 58 | } 59 | return date.get('year') + '-' + (date.get('month') + 1) + '-' + date.get('date') + ' ' + date.get('hour') + ':' + date.get('minute') + ':' + second; 60 | }; 61 | 62 | }).call(this); 63 | -------------------------------------------------------------------------------- /model/Video.coffee: -------------------------------------------------------------------------------- 1 | 2 | # video model 3 | 4 | class Video 5 | constructor: (@video) -> 6 | @id = @video.id 7 | @name = @video.name 8 | @path = @video.path 9 | @format = @video.format 10 | @time = @video.time 11 | @poster = @video.poster 12 | @duration = @video.duration 13 | @play_count = @video.play_count 14 | @barrage = @video.barrage 15 | @barrage_count = @video.barrage_count 16 | 17 | save: (callback) -> 18 | 19 | callback = callback || () -> 20 | 21 | if global.db 22 | global.db.collection 'video', (err, collection) => 23 | if err 24 | console.log err 25 | return callback err 26 | collection.insert @video, (err, v) => 27 | if err 28 | return callback err 29 | callback err, this 30 | 31 | incPlayCount: () -> 32 | if global.db 33 | global.db.collection 'video', (err, collection) => 34 | if collection 35 | collection.update({id: this.id}, {"$inc": {"play_count": 1}} ) 36 | 37 | @findOne: (id, callback) -> 38 | callback = callback || () -> 39 | 40 | if global.db 41 | global.db.collection 'video', (err, collection) -> 42 | if err 43 | console.log err 44 | return callback err 45 | collection.findOne {id: parseInt(id)}, (err, v) -> 46 | if err 47 | return callback err,v 48 | callback err, new Video(v) 49 | 50 | @find: (from, to, callback) -> 51 | callback = callback || () -> 52 | 53 | if global.db 54 | global.db.collection 'video', (err, collection) -> 55 | if err 56 | console.log err 57 | return callback err 58 | collection.find({}, {limit: to, skip: from,fields:{'barrage':0}, sort: [['time', 'desc'], ['barrage.time','desc']]}).toArray (err, result)-> 59 | if err 60 | return callback err, result 61 | 62 | videos = [] 63 | for video in result 64 | videos.push new Video(video) 65 | 66 | callback err, videos 67 | 68 | @addBarrage: (id, barrage, callback) -> 69 | callback = callback || () -> 70 | callback() 71 | if id && barrage && global.db 72 | global.db.collection 'video', (err, collection) => 73 | if err 74 | return callback err 75 | collection.update {id:id}, {"$push": {"barrage": barrage}} 76 | collection.update {id:id}, {"$inc": {"barrage_count": 1}} 77 | 78 | 79 | module.exports = Video 80 | -------------------------------------------------------------------------------- /public/js/video.coffee: -------------------------------------------------------------------------------- 1 | # 2 | $('body').css {'background-color': '#333'} 3 | 4 | path = window.location.pathname 5 | vid = parseInt path.substr(path.lastIndexOf('/')+1) 6 | barrages = [] 7 | 8 | if isNaN(vid) 9 | window.location.href = '/404.html' 10 | 11 | $barrage = $("#barrage") 12 | $videoWrap = $('#video-wrap') 13 | $info = $('#info') 14 | 15 | $info.find('.list-group-item').each (index, html) -> 16 | $item = $(html) 17 | item = { 18 | 'time': $item.data('time'), 19 | 'duration': $item.data('duration'), 20 | 'content': $item.attr('title') 21 | } 22 | barrages.push(item) 23 | 24 | # video player 25 | videojs.options.flash.swf = '/lib/videojs/video-js.swf' 26 | 27 | videoPlayer = videojs 'video' 28 | preTime = 0 29 | flag = 0 30 | top = 0 31 | 32 | videoPlayer.on 'timeupdate', (e)-> 33 | curTime = parseInt(videoPlayer.currentTime()) 34 | if curTime == preTime 35 | return 36 | 37 | for barrage in barrages 38 | if barrage.duration == curTime 39 | $item = $('
'+barrage.content+'
') 40 | $item.appendTo($("#video")) 41 | $item.css {'top': top+'px'} 42 | 43 | top = top + 30 44 | flag++ 45 | if flag>5 46 | flag = 0 47 | top = 0 48 | 49 | $item.animate({'left': '-50%'}, 15000, 'linear', ()-> 50 | $(this).remove() 51 | ) 52 | preTime = curTime 53 | 54 | # adjust the video container while window size change 55 | winResize = (e) -> 56 | width = $videoWrap.width() 57 | height = 9/16 * width 58 | $videoWrap.height height 59 | $info.find('.panel-body').css {'max-height': height, 'overflow': 'auto'} 60 | 61 | winResize() 62 | 63 | $(window).on 'resize', (e) -> 64 | winResize(e) 65 | 66 | 67 | # connect to server 68 | 69 | socket = io.connect('/video') 70 | 71 | socket.on 'connected', () -> 72 | socket.emit 'connected', {id: vid} 73 | 74 | socket.on 'connected'+vid, (data)-> 75 | $('#p-count').text data.count 76 | 77 | socket.on 'barrage'+vid, (barrage)-> 78 | barrages.push(barrage) 79 | $li = $('
  • '+barrage.content+'
  • ') 80 | $li.css {'height': '30px', 'line-height': '30px', 'padding': '0 10px', 'overflow': 'hidden'} 81 | $info.find('.list-group').prepend($li) 82 | $('#c-count').text(barrages.length) 83 | 84 | 85 | $barrage.find('input').keyup (e) -> 86 | if e.keyCode == 13 87 | barrage = $(this).val() 88 | $(this).val('') 89 | socket.emit 'barrage'+vid, {duration: parseInt(videoPlayer.currentTime() + 1), content:barrage} 90 | 91 | -------------------------------------------------------------------------------- /public/css/style.less: -------------------------------------------------------------------------------- 1 | @padding: 10px; 2 | @margin: 10px 0; 3 | @nav-height: 60px; 4 | @main-font: "微软雅黑","Helvetica Neue",Helvetica,Arial,sans-serif !important; 5 | 6 | .mt10 {margin-top: 10px;} 7 | .mt150 {margin-top: 150px;} 8 | 9 | body{padding: 0; margin: 0; font-family:@main-font; background-color: #f8f8f8;} 10 | .messages {height: 300px; border: 1px solid #CCC; overflow: hidden; overflow-y: auto; margin: @margin;} 11 | .navbar { 12 | background-color: #1b76b5; 13 | .container{ 14 | height: @nav-height; 15 | line-height: @nav-height; 16 | a { 17 | text-decoration: none; 18 | color: #fff; 19 | font-size: 14px; 20 | text-shadow: 1px 1px 5px #333; 21 | } 22 | a:hover{ 23 | text-decoration: underline; 24 | } 25 | .logo{ 26 | float: left; 27 | display: inline-block; 28 | img { 29 | height: @nav-height; 30 | width: @nav-height; 31 | margin-top: -20px; 32 | } 33 | a{ 34 | display: inline-block; 35 | font-size: 28px; 36 | padding-left: 5px; 37 | } 38 | a:hover{ 39 | text-decoration: none; 40 | } 41 | } 42 | .link { 43 | display: inline-block; 44 | float: right; 45 | a{ 46 | padding: 10px 15px; 47 | } 48 | } 49 | } 50 | } 51 | .add-video { 52 | margin-top: 50px; 53 | .progress { 54 | height: 15px; 55 | margin-top: 10px; 56 | .progress-bar { 57 | line-height: 15px; 58 | } 59 | } 60 | } 61 | 62 | .video-list{ 63 | margin-top: 20px; 64 | list-style: none; 65 | font-size: 12px; 66 | .detail{ 67 | padding: 10px; 68 | div { 69 | line-height: 1.5em; 70 | max-width: 200px; 71 | overflow: hidden; 72 | text-overflow: ellipsis; 73 | white-space: nowrap; 74 | } 75 | } 76 | } 77 | 78 | .video-js { 79 | margin: 0 auto; 80 | width: 100% !important; 81 | height: 100% !important; 82 | } 83 | 84 | #info{ 85 | font-size: 12px; 86 | .panel-heading { 87 | overflow: hidden; 88 | } 89 | .list-group{ 90 | padding: 0; 91 | .list-group-item{ 92 | padding: 0 10px; 93 | height: 30px; 94 | line-height: 30px; 95 | overflow: hidden; 96 | text-overflow: ellipsis; 97 | white-space: nowrap; 98 | } 99 | } 100 | } 101 | 102 | #video{ 103 | position: relative; 104 | overflow: hidden; 105 | white-space: nowrap; 106 | font-family: @main-font; 107 | .barrage { 108 | white-space: nowrap; 109 | font-size: 14px; 110 | text-shadow: 1px 1px 1px #000; 111 | position: absolute; 112 | color: #fff; 113 | left: 100%; 114 | top: 0; 115 | display: inline-block; 116 | z-index: 2147483648; 117 | } 118 | } 119 | 120 | #video.vjs-fullscreen { 121 | position: fixed; 122 | .barrage { 123 | font-size: 24px; 124 | } 125 | } -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | .mt10 { 2 | margin-top: 10px; 3 | } 4 | .mt150 { 5 | margin-top: 150px; 6 | } 7 | body { 8 | padding: 0; 9 | margin: 0; 10 | font-family: "微软雅黑", "Helvetica Neue", Helvetica, Arial, sans-serif; 11 | background-color: #f8f8f8; 12 | } 13 | .messages { 14 | height: 300px; 15 | border: 1px solid #CCC; 16 | overflow: hidden; 17 | overflow-y: auto; 18 | margin: 10px 0; 19 | } 20 | .navbar { 21 | background-color: #1b76b5; 22 | } 23 | .navbar .container { 24 | height: 60px; 25 | line-height: 60px; 26 | } 27 | .navbar .container a { 28 | text-decoration: none; 29 | color: #fff; 30 | font-size: 14px; 31 | text-shadow: 1px 1px 5px #333; 32 | } 33 | .navbar .container a:hover { 34 | text-decoration: underline; 35 | } 36 | .navbar .container .logo { 37 | float: left; 38 | display: inline-block; 39 | } 40 | .navbar .container .logo img { 41 | height: 60px; 42 | width: 60px; 43 | margin-top: -20px; 44 | } 45 | .navbar .container .logo a { 46 | display: inline-block; 47 | font-size: 28px; 48 | padding-left: 5px; 49 | } 50 | .navbar .container .logo a:hover { 51 | text-decoration: none; 52 | } 53 | .navbar .container .link { 54 | display: inline-block; 55 | float: right; 56 | } 57 | .navbar .container .link a { 58 | padding: 10px 15px; 59 | } 60 | .add-video { 61 | margin-top: 50px; 62 | } 63 | .add-video .progress { 64 | height: 15px; 65 | margin-top: 10px; 66 | } 67 | .add-video .progress .progress-bar { 68 | line-height: 15px; 69 | } 70 | .video-list { 71 | margin-top: 20px; 72 | list-style: none; 73 | font-size: 12px; 74 | } 75 | .video-list .detail { 76 | padding: 10px; 77 | } 78 | .video-list .detail div { 79 | line-height: 1.5em; 80 | max-width: 200px; 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | white-space: nowrap; 84 | } 85 | .video-js { 86 | margin: 0 auto; 87 | width: 100% !important; 88 | height: 100% !important; 89 | } 90 | #info { 91 | font-size: 12px; 92 | } 93 | #info .panel-heading { 94 | overflow: hidden; 95 | } 96 | #info .list-group { 97 | padding: 0; 98 | } 99 | #info .list-group .list-group-item { 100 | padding: 0 10px; 101 | height: 30px; 102 | line-height: 30px; 103 | overflow: hidden; 104 | text-overflow: ellipsis; 105 | white-space: nowrap; 106 | } 107 | #video { 108 | position: relative; 109 | overflow: hidden; 110 | white-space: nowrap; 111 | font-family: "微软雅黑", "Helvetica Neue", Helvetica, Arial, sans-serif; 112 | } 113 | #video .barrage { 114 | white-space: nowrap; 115 | font-size: 14px; 116 | text-shadow: 1px 1px 1px #000; 117 | position: absolute; 118 | color: #fff; 119 | left: 100%; 120 | top: 0; 121 | display: inline-block; 122 | z-index: 2147483648; 123 | } 124 | #video.vjs-fullscreen { 125 | position: fixed; 126 | } 127 | #video.vjs-fullscreen .barrage { 128 | font-size: 24px; 129 | } 130 | -------------------------------------------------------------------------------- /public/js/add_video.coffee: -------------------------------------------------------------------------------- 1 | # 2 | $progress = $('#progress') 3 | $progressBar = $progress.find('.progress-bar') 4 | $uploadBtn = $('#upload-btn') 5 | $videoName = $("#video-name") 6 | $submitBth = $('#submit_btn') 7 | $messagePanel = $("#message") 8 | 9 | showError = (text) -> 10 | $messagePanel.removeClass('hidden alert-info').addClass('alert-danger').text text 11 | showInfo = (text) -> 12 | $messagePanel.removeClass('hidden alert-danger').addClass('alert-info').text text 13 | 14 | r = new Resumable({ 15 | target: '/upload' 16 | chunkSize: 1*1024*1024, 17 | simultaneousUploads: 4, 18 | testChunks: false, 19 | throttleProgressCallbacks: 1, 20 | maxFiles:1, 21 | fileType: ['mp4'], 22 | fileTypeErrorCallback: (file, errors) -> 23 | showError '请上传MP4格式的视频!' 24 | }) 25 | 26 | 27 | 28 | 29 | 30 | if !r.support 31 | $("#upload-form").hide() 32 | showError '您现在用的浏览器不支持该功能!' 33 | else 34 | r.assignDrop $('#upload-form')[0] 35 | r.assignBrowse $('#upload-btn')[0] 36 | 37 | r.on 'fileAdded', (file) -> 38 | $messagePanel.addClass('hidden') 39 | $progress.removeClass('hidden') 40 | $uploadBtn.parent().addClass('hidden') 41 | r.upload() 42 | 43 | 44 | r.on 'pause', ()-> 45 | # todo 46 | 47 | r.on 'complete', () -> 48 | # todo 49 | 50 | 51 | r.on 'fileSuccess', (file, message) -> 52 | $videoName.removeClass('hidden') 53 | $videoName.find('input[name=name]').val(file.fileName.substr(0, file.fileName.lastIndexOf('.'))) 54 | 55 | $('input[name=filename]').val(file.fileName) 56 | 57 | # merge file 58 | $.post '/video/merge', {filename: file.fileName, identifier: file.uniqueIdentifier}, (res) -> 59 | if res.status == 'success' 60 | data = res.data || {} 61 | $('input[name=time]').val(data.time) 62 | $('input[name=format]').val(data.format) 63 | $('input[name=path]').val(data.path) 64 | $('input[name=poster]').val(data.poster) 65 | $('input[name=duration]').val(data.duration) 66 | 67 | if data.poster 68 | $("#thumb").removeClass('hidden').find('img').attr('src', '/video/thumbnail/'+data.poster) 69 | 70 | $submitBth.removeClass('hidden') 71 | $messagePanel.addClass('hidden') 72 | else 73 | showError '合并文件失败!' 74 | 75 | 76 | r.on 'fileError', (file, message) -> 77 | showError '上传出错' 78 | r.cancel() 79 | 80 | r.on 'fileProgress', (file, message) -> 81 | progress = Math.floor file.progress()*100 82 | $progressBar.css {'width': progress+'%'} 83 | $progressBar.find('span').text progress + '%' 84 | 85 | 86 | r.on 'cancel', ()-> 87 | # todo 88 | 89 | r.on 'uploadStart', () -> 90 | # todo 91 | 92 | $submitBth.click () -> 93 | data = $("#upload-form").serialize() 94 | $.ajax { 95 | url: '/video/add', 96 | data: data, 97 | type: 'POST', 98 | dataType: 'json', 99 | beforeSend: () -> 100 | $submitBth.addClass('hidden') 101 | complete: () -> 102 | $submitBth.removeClass('hidden') 103 | success: (res) -> 104 | if res.status == 'success' 105 | window.location.href = '/video/av/'+res.data 106 | else 107 | console.log res.message 108 | showError '添加视频出错!' 109 | } 110 | -------------------------------------------------------------------------------- /views/add_video.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.twig' %} 2 | 3 | {% block content %} 4 |
    5 | 6 |
    7 |
    上传视频
    8 |
    9 |
    10 | 16 |
    17 | 18 |
    19 | 上传 20 |
    21 | 33 |
    34 | 40 |
    41 |
    42 | 43 |
    44 |
    45 | 46 | 47 | 48 | 49 | 50 |
    51 |
    52 | 53 |
    54 |
    55 | {% endblock %} 56 | 57 | {% block footer %} 58 | 59 | 60 | 61 | {% endblock %} -------------------------------------------------------------------------------- /public/js/video.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | (function() { 3 | var $barrage, $info, $videoWrap, barrages, flag, path, preTime, socket, top, vid, videoPlayer, winResize; 4 | 5 | $('body').css({ 6 | 'background-color': '#333' 7 | }); 8 | 9 | path = window.location.pathname; 10 | 11 | vid = parseInt(path.substr(path.lastIndexOf('/') + 1)); 12 | 13 | barrages = []; 14 | 15 | if (isNaN(vid)) { 16 | window.location.href = '/404.html'; 17 | } 18 | 19 | $barrage = $("#barrage"); 20 | 21 | $videoWrap = $('#video-wrap'); 22 | 23 | $info = $('#info'); 24 | 25 | $info.find('.list-group-item').each(function(index, html) { 26 | var $item, item; 27 | $item = $(html); 28 | item = { 29 | 'time': $item.data('time'), 30 | 'duration': $item.data('duration'), 31 | 'content': $item.attr('title') 32 | }; 33 | return barrages.push(item); 34 | }); 35 | 36 | videojs.options.flash.swf = '/lib/videojs/video-js.swf'; 37 | 38 | videoPlayer = videojs('video'); 39 | 40 | preTime = 0; 41 | 42 | flag = 0; 43 | 44 | top = 0; 45 | 46 | videoPlayer.on('timeupdate', function(e) { 47 | var $item, barrage, curTime, _i, _len; 48 | curTime = parseInt(videoPlayer.currentTime()); 49 | if (curTime === preTime) { 50 | return; 51 | } 52 | for (_i = 0, _len = barrages.length; _i < _len; _i++) { 53 | barrage = barrages[_i]; 54 | if (barrage.duration === curTime) { 55 | $item = $('
    ' + barrage.content + '
    '); 56 | $item.appendTo($("#video")); 57 | $item.css({ 58 | 'top': top + 'px' 59 | }); 60 | top = top + 30; 61 | flag++; 62 | if (flag > 5) { 63 | flag = 0; 64 | top = 0; 65 | } 66 | $item.animate({ 67 | 'left': '-50%' 68 | }, 15000, 'linear', function() { 69 | return $(this).remove(); 70 | }); 71 | } 72 | } 73 | return preTime = curTime; 74 | }); 75 | 76 | winResize = function(e) { 77 | var height, width; 78 | width = $videoWrap.width(); 79 | height = 9 / 16 * width; 80 | $videoWrap.height(height); 81 | return $info.find('.panel-body').css({ 82 | 'max-height': height, 83 | 'overflow': 'auto' 84 | }); 85 | }; 86 | 87 | winResize(); 88 | 89 | $(window).on('resize', function(e) { 90 | return winResize(e); 91 | }); 92 | 93 | socket = io.connect('/video'); 94 | 95 | socket.on('connected', function() { 96 | return socket.emit('connected', { 97 | id: vid 98 | }); 99 | }); 100 | 101 | socket.on('connected' + vid, function(data) { 102 | return $('#p-count').text(data.count); 103 | }); 104 | 105 | socket.on('barrage' + vid, function(barrage) { 106 | var $li; 107 | barrages.push(barrage); 108 | $li = $('
  • ' + barrage.content + '
  • '); 109 | $li.css({ 110 | 'height': '30px', 111 | 'line-height': '30px', 112 | 'padding': '0 10px', 113 | 'overflow': 'hidden' 114 | }); 115 | $info.find('.list-group').prepend($li); 116 | return $('#c-count').text(barrages.length); 117 | }); 118 | 119 | $barrage.find('input').keyup(function(e) { 120 | var barrage; 121 | if (e.keyCode === 13) { 122 | barrage = $(this).val(); 123 | $(this).val(''); 124 | return socket.emit('barrage' + vid, { 125 | duration: parseInt(videoPlayer.currentTime() + 1), 126 | content: barrage 127 | }); 128 | } 129 | }); 130 | 131 | }).call(this); 132 | 133 | //# sourceMappingURL=video.map 134 | -------------------------------------------------------------------------------- /public/js/add_video.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var $messagePanel, $progress, $progressBar, $submitBth, $uploadBtn, $videoName, r, showError, showInfo; 4 | 5 | $progress = $('#progress'); 6 | 7 | $progressBar = $progress.find('.progress-bar'); 8 | 9 | $uploadBtn = $('#upload-btn'); 10 | 11 | $videoName = $("#video-name"); 12 | 13 | $submitBth = $('#submit_btn'); 14 | 15 | $messagePanel = $("#message"); 16 | 17 | showError = function(text) { 18 | return $messagePanel.removeClass('hidden alert-info').addClass('alert-danger').text(text); 19 | }; 20 | 21 | showInfo = function(text) { 22 | return $messagePanel.removeClass('hidden alert-danger').addClass('alert-info').text(text); 23 | }; 24 | 25 | r = new Resumable({ 26 | target: '/upload', 27 | chunkSize: 1 * 1024 * 1024, 28 | simultaneousUploads: 4, 29 | testChunks: false, 30 | throttleProgressCallbacks: 1, 31 | maxFiles: 1, 32 | fileType: ['mp4'], 33 | fileTypeErrorCallback: function(file, errors) { 34 | return showError('请上传MP4格式的视频!'); 35 | } 36 | }); 37 | 38 | if (!r.support) { 39 | $("#upload-form").hide(); 40 | showError('您现在用的浏览器不支持该功能!'); 41 | } else { 42 | r.assignDrop($('#upload-form')[0]); 43 | r.assignBrowse($('#upload-btn')[0]); 44 | r.on('fileAdded', function(file) { 45 | $messagePanel.addClass('hidden'); 46 | $progress.removeClass('hidden'); 47 | $uploadBtn.parent().addClass('hidden'); 48 | return r.upload(); 49 | }); 50 | r.on('pause', function() {}); 51 | r.on('complete', function() {}); 52 | r.on('fileSuccess', function(file, message) { 53 | $videoName.removeClass('hidden'); 54 | $videoName.find('input[name=name]').val(file.fileName.substr(0, file.fileName.lastIndexOf('.'))); 55 | $('input[name=filename]').val(file.fileName); 56 | return $.post('/video/merge', { 57 | filename: file.fileName, 58 | identifier: file.uniqueIdentifier 59 | }, function(res) { 60 | var data; 61 | if (res.status === 'success') { 62 | data = res.data || {}; 63 | $('input[name=time]').val(data.time); 64 | $('input[name=format]').val(data.format); 65 | $('input[name=path]').val(data.path); 66 | $('input[name=poster]').val(data.poster); 67 | $('input[name=duration]').val(data.duration); 68 | if (data.poster) { 69 | $("#thumb").removeClass('hidden').find('img').attr('src', '/video/thumbnail/' + data.poster); 70 | } 71 | $submitBth.removeClass('hidden'); 72 | return $messagePanel.addClass('hidden'); 73 | } else { 74 | return showError('合并文件失败!'); 75 | } 76 | }); 77 | }); 78 | r.on('fileError', function(file, message) { 79 | showError('上传出错'); 80 | return r.cancel(); 81 | }); 82 | r.on('fileProgress', function(file, message) { 83 | var progress; 84 | progress = Math.floor(file.progress() * 100); 85 | $progressBar.css({ 86 | 'width': progress + '%' 87 | }); 88 | return $progressBar.find('span').text(progress + '%'); 89 | }); 90 | r.on('cancel', function() {}); 91 | r.on('uploadStart', function() {}); 92 | $submitBth.click(function() { 93 | var data; 94 | data = $("#upload-form").serialize(); 95 | return $.ajax({ 96 | url: '/video/add', 97 | data: data, 98 | type: 'POST', 99 | dataType: 'json', 100 | beforeSend: function() { 101 | return $submitBth.addClass('hidden'); 102 | }, 103 | complete: function() { 104 | return $submitBth.removeClass('hidden'); 105 | }, 106 | success: function(res) { 107 | if (res.status === 'success') { 108 | return window.location.href = '/video/av/' + res.data; 109 | } else { 110 | console.log(res.message); 111 | return showError('添加视频出错!'); 112 | } 113 | } 114 | }); 115 | }); 116 | } 117 | 118 | }).call(this); 119 | -------------------------------------------------------------------------------- /model/Video.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Video; 4 | 5 | Video = (function() { 6 | function Video(video) { 7 | this.video = video; 8 | this.id = this.video.id; 9 | this.name = this.video.name; 10 | this.path = this.video.path; 11 | this.format = this.video.format; 12 | this.time = this.video.time; 13 | this.poster = this.video.poster; 14 | this.duration = this.video.duration; 15 | this.play_count = this.video.play_count; 16 | this.barrage = this.video.barrage; 17 | this.barrage_count = this.video.barrage_count; 18 | } 19 | 20 | Video.prototype.save = function(callback) { 21 | var _this = this; 22 | callback = callback || function() {}; 23 | if (global.db) { 24 | return global.db.collection('video', function(err, collection) { 25 | if (err) { 26 | console.log(err); 27 | return callback(err); 28 | } 29 | return collection.insert(_this.video, function(err, v) { 30 | if (err) { 31 | return callback(err); 32 | } 33 | return callback(err, _this); 34 | }); 35 | }); 36 | } 37 | }; 38 | 39 | Video.prototype.incPlayCount = function() { 40 | var _this = this; 41 | if (global.db) { 42 | return global.db.collection('video', function(err, collection) { 43 | if (collection) { 44 | return collection.update({ 45 | id: _this.id 46 | }, { 47 | "$inc": { 48 | "play_count": 1 49 | } 50 | }); 51 | } 52 | }); 53 | } 54 | }; 55 | 56 | Video.findOne = function(id, callback) { 57 | callback = callback || function() {}; 58 | if (global.db) { 59 | return global.db.collection('video', function(err, collection) { 60 | if (err) { 61 | console.log(err); 62 | return callback(err); 63 | } 64 | return collection.findOne({ 65 | id: parseInt(id) 66 | }, function(err, v) { 67 | if (err) { 68 | return callback(err, v); 69 | } 70 | return callback(err, new Video(v)); 71 | }); 72 | }); 73 | } 74 | }; 75 | 76 | Video.find = function(from, to, callback) { 77 | callback = callback || function() {}; 78 | if (global.db) { 79 | return global.db.collection('video', function(err, collection) { 80 | if (err) { 81 | console.log(err); 82 | return callback(err); 83 | } 84 | return collection.find({}, { 85 | limit: to, 86 | skip: from, 87 | fields: { 88 | 'barrage': 0 89 | }, 90 | sort: [['time', 'desc'], ['barrage.time', 'desc']] 91 | }).toArray(function(err, result) { 92 | var video, videos, _i, _len; 93 | if (err) { 94 | return callback(err, result); 95 | } 96 | videos = []; 97 | for (_i = 0, _len = result.length; _i < _len; _i++) { 98 | video = result[_i]; 99 | videos.push(new Video(video)); 100 | } 101 | return callback(err, videos); 102 | }); 103 | }); 104 | } 105 | }; 106 | 107 | Video.addBarrage = function(id, barrage, callback) { 108 | var _this = this; 109 | callback = callback || function() {}; 110 | callback(); 111 | if (id && barrage && global.db) { 112 | return global.db.collection('video', function(err, collection) { 113 | if (err) { 114 | return callback(err); 115 | } 116 | collection.update({ 117 | id: id 118 | }, { 119 | "$push": { 120 | "barrage": barrage 121 | } 122 | }); 123 | return collection.update({ 124 | id: id 125 | }, { 126 | "$inc": { 127 | "barrage_count": 1 128 | } 129 | }); 130 | }); 131 | } 132 | }; 133 | 134 | return Video; 135 | 136 | })(); 137 | 138 | module.exports = Video; 139 | 140 | }).call(this); 141 | -------------------------------------------------------------------------------- /routes/video.coffee: -------------------------------------------------------------------------------- 1 | 2 | # video controller 3 | os_path = require 'path' 4 | fs = require 'fs' 5 | settings = require '../config' 6 | Video = require '../model/Video' 7 | util = require '../modules/util' 8 | escape = require 'escape-html' 9 | 10 | module.exports = (app) -> 11 | 12 | # video list 13 | app.get '/video', (req, res) -> 14 | Video.find 0, 0, (err, result)-> 15 | if err 16 | res.redirect '/404.html' 17 | else 18 | for video in result 19 | video.time = util.getFormatDate(video.time) 20 | if !video.play_count 21 | video.play_count = 0 22 | if !video.barrage_count 23 | video.barrage_count = 0 24 | 25 | res.render 'video_list', {videos: result, title: '视频列表', navAction: {url: '/video/add', text: '上传视频'}} 26 | 27 | # view video 28 | app.get '/video/av/:id', (req, res) -> 29 | id = req.params.id 30 | Video.findOne id, (err, v) -> 31 | if err 32 | res.redirect '/404.html' 33 | return 34 | v.incPlayCount() 35 | if !v.barrage_count 36 | v.barrage_count = 0 37 | 38 | if v.barrage 39 | v.barrage.sort (a, b) -> 40 | return b.time-a.time 41 | res.render 'video', {video: v, title: v.name} 42 | 43 | # add a video 44 | app.get '/video/add', (req, res) -> 45 | res.render 'add_video', {title: 'Add Video', navAction:{url: '/video', text: '返回列表'}} 46 | 47 | # add a video action 48 | app.post '/video/add', (req, res) -> 49 | video = { 50 | name : escape(req.body.name), 51 | path : req.body.path, 52 | format : req.body.format, 53 | time : req.body.time, 54 | poster : req.body.poster, 55 | duration : req.body.duration 56 | } 57 | 58 | getNextId 'video_id', (err, result) -> 59 | if err 60 | json_response res, 'error', null, err.message 61 | video.id = result.seq 62 | video = new Video(video) 63 | video.save (err, v)-> 64 | if err 65 | json_response res, 'error', null, err.message 66 | json_response(res, 'success', v.id) 67 | 68 | 69 | #video merge 70 | app.post '/video/merge', (req, res) -> 71 | filename = req.body.filename 72 | identifier = req.body.identifier 73 | 74 | util = require '../modules/util' 75 | util.mergeFile filename, identifier, (err, path, format, time) -> 76 | if err 77 | json_response res, 'error', null, err.message 78 | else 79 | # screenshot 80 | ffmpeg = require 'fluent-ffmpeg' 81 | videoPath = os_path.join(__dirname, '../uploads/'+path) 82 | thumbPath = os_path.join(__dirname, '../uploads/thumb/') 83 | video = {path: path, format: format, time: time} 84 | 85 | proc = new ffmpeg {source: videoPath} 86 | proc.setFfmpegPath settings.ffmpegPath 87 | proc.withSize('150x150').takeScreenshots {count:1, timemarks: ['10%'], filename: '%f_thumb_%wx%h_%i'}, thumbPath, (err, fileName) -> 88 | if !err 89 | video.poster = fileName 90 | 91 | # get duration 92 | meta = new ffmpeg.Metadata videoPath, (info, err) -> 93 | if !err 94 | video.duration = info.durationraw.substr(0, info.durationraw.lastIndexOf('.')) 95 | json_response res, 'success', video 96 | 97 | 98 | # video thumbnail 99 | app.get '/video/thumbnail/:filename?*', (req, res) -> 100 | thumbPath = os_path.join __dirname, '../uploads/thumb/'+req.params.filename 101 | defaultPath = os_path.join __dirname, '../uploads/thumb/thumb_default.png' 102 | fs.exists thumbPath, (exists) -> 103 | if exists 104 | res.sendfile thumbPath 105 | else 106 | res.sendfile defaultPath 107 | 108 | 109 | # video convert 110 | app.post '/video/convert', (req, res) -> 111 | res.end 'forbid' 112 | 113 | ffmpeg = require 'fluent-ffmpeg' 114 | 115 | sourcePath = req.body.video_path 116 | 117 | console.log sourcePath 118 | 119 | if !sourcePath 120 | json_response res, 'error', null, 'File not found when converting!' 121 | return 122 | 123 | sourceFormat = sourcePath.substr sourcePath.lastIndexOf('.')+1 124 | if sourceFormat == 'mp4' 125 | json_response res, 'success', {'format': 'mp4', 'path': sourcePath} 126 | return 127 | 128 | tempPath = targetPath = sourcePath.substr(0, sourcePath.lastIndexOf('.'))+'.mp4' 129 | 130 | uploadPath = os_path.join(__dirname, '../uploads/') 131 | sourcePath = os_path.join(uploadPath, sourcePath) 132 | targetPath = os_path.join(uploadPath, targetPath) 133 | 134 | proc = new ffmpeg {source: sourcePath, timeout: 3600} 135 | 136 | proc.setFfmpegPath settings.ffmpegPath 137 | 138 | proc.withVideoCodec('libx264').onProgress((info)-> 139 | private_video_sockets.emit 'convert progress', info 140 | 141 | ).toFormat('mp4').saveToFile targetPath, (stdout, stderr) -> 142 | json_response res, 'success', {'format': 'mp4', 'path': tempPath} 143 | -------------------------------------------------------------------------------- /routes/video.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.6.3 2 | (function() { 3 | var Video, escape, fs, os_path, settings, util; 4 | 5 | os_path = require('path'); 6 | 7 | fs = require('fs'); 8 | 9 | settings = require('../config'); 10 | 11 | Video = require('../model/Video'); 12 | 13 | util = require('../modules/util'); 14 | 15 | escape = require('escape-html'); 16 | 17 | module.exports = function(app) { 18 | app.get('/video', function(req, res) { 19 | return Video.find(0, 0, function(err, result) { 20 | var video, _i, _len; 21 | if (err) { 22 | return res.redirect('/404.html'); 23 | } else { 24 | for (_i = 0, _len = result.length; _i < _len; _i++) { 25 | video = result[_i]; 26 | video.time = util.getFormatDate(video.time); 27 | if (!video.play_count) { 28 | video.play_count = 0; 29 | } 30 | if (!video.barrage_count) { 31 | video.barrage_count = 0; 32 | } 33 | } 34 | return res.render('video_list', { 35 | videos: result, 36 | title: '视频列表', 37 | navAction: { 38 | url: '/video/add', 39 | text: '上传视频' 40 | } 41 | }); 42 | } 43 | }); 44 | }); 45 | app.get('/video/av/:id', function(req, res) { 46 | var id; 47 | id = req.params.id; 48 | return Video.findOne(id, function(err, v) { 49 | if (err) { 50 | res.redirect('/404.html'); 51 | return; 52 | } 53 | v.incPlayCount(); 54 | if (!v.barrage_count) { 55 | v.barrage_count = 0; 56 | } 57 | if (v.barrage) { 58 | v.barrage.sort(function(a, b) { 59 | return b.time - a.time; 60 | }); 61 | } 62 | return res.render('video', { 63 | video: v, 64 | title: v.name 65 | }); 66 | }); 67 | }); 68 | app.get('/video/add', function(req, res) { 69 | return res.render('add_video', { 70 | title: 'Add Video', 71 | navAction: { 72 | url: '/video', 73 | text: '返回列表' 74 | } 75 | }); 76 | }); 77 | app.post('/video/add', function(req, res) { 78 | var video; 79 | video = { 80 | name: escape(req.body.name), 81 | path: req.body.path, 82 | format: req.body.format, 83 | time: req.body.time, 84 | poster: req.body.poster, 85 | duration: req.body.duration 86 | }; 87 | return getNextId('video_id', function(err, result) { 88 | if (err) { 89 | json_response(res, 'error', null, err.message); 90 | } 91 | video.id = result.seq; 92 | video = new Video(video); 93 | return video.save(function(err, v) { 94 | if (err) { 95 | json_response(res, 'error', null, err.message); 96 | } 97 | return json_response(res, 'success', v.id); 98 | }); 99 | }); 100 | }); 101 | app.post('/video/merge', function(req, res) { 102 | var filename, identifier; 103 | filename = req.body.filename; 104 | identifier = req.body.identifier; 105 | util = require('../modules/util'); 106 | return util.mergeFile(filename, identifier, function(err, path, format, time) { 107 | var ffmpeg, proc, thumbPath, video, videoPath; 108 | if (err) { 109 | return json_response(res, 'error', null, err.message); 110 | } else { 111 | ffmpeg = require('fluent-ffmpeg'); 112 | videoPath = os_path.join(__dirname, '../uploads/' + path); 113 | thumbPath = os_path.join(__dirname, '../uploads/thumb/'); 114 | video = { 115 | path: path, 116 | format: format, 117 | time: time 118 | }; 119 | proc = new ffmpeg({ 120 | source: videoPath 121 | }); 122 | proc.setFfmpegPath(settings.ffmpegPath); 123 | return proc.withSize('150x150').takeScreenshots({ 124 | count: 1, 125 | timemarks: ['10%'], 126 | filename: '%f_thumb_%wx%h_%i' 127 | }, thumbPath, function(err, fileName) { 128 | var meta; 129 | if (!err) { 130 | video.poster = fileName; 131 | } 132 | return meta = new ffmpeg.Metadata(videoPath, function(info, err) { 133 | if (!err) { 134 | video.duration = info.durationraw.substr(0, info.durationraw.lastIndexOf('.')); 135 | } 136 | return json_response(res, 'success', video); 137 | }); 138 | }); 139 | } 140 | }); 141 | }); 142 | app.get('/video/thumbnail/:filename?*', function(req, res) { 143 | var defaultPath, thumbPath; 144 | thumbPath = os_path.join(__dirname, '../uploads/thumb/' + req.params.filename); 145 | defaultPath = os_path.join(__dirname, '../uploads/thumb/thumb_default.png'); 146 | return fs.exists(thumbPath, function(exists) { 147 | if (exists) { 148 | return res.sendfile(thumbPath); 149 | } else { 150 | return res.sendfile(defaultPath); 151 | } 152 | }); 153 | }); 154 | return app.post('/video/convert', function(req, res) { 155 | var ffmpeg, proc, sourceFormat, sourcePath, targetPath, tempPath, uploadPath; 156 | res.end('forbid'); 157 | ffmpeg = require('fluent-ffmpeg'); 158 | sourcePath = req.body.video_path; 159 | console.log(sourcePath); 160 | if (!sourcePath) { 161 | json_response(res, 'error', null, 'File not found when converting!'); 162 | return; 163 | } 164 | sourceFormat = sourcePath.substr(sourcePath.lastIndexOf('.') + 1); 165 | if (sourceFormat === 'mp4') { 166 | json_response(res, 'success', { 167 | 'format': 'mp4', 168 | 'path': sourcePath 169 | }); 170 | return; 171 | } 172 | tempPath = targetPath = sourcePath.substr(0, sourcePath.lastIndexOf('.')) + '.mp4'; 173 | uploadPath = os_path.join(__dirname, '../uploads/'); 174 | sourcePath = os_path.join(uploadPath, sourcePath); 175 | targetPath = os_path.join(uploadPath, targetPath); 176 | proc = new ffmpeg({ 177 | source: sourcePath, 178 | timeout: 3600 179 | }); 180 | proc.setFfmpegPath(settings.ffmpegPath); 181 | return proc.withVideoCodec('libx264').onProgress(function(info) { 182 | return private_video_sockets.emit('convert progress', info); 183 | }).toFormat('mp4').saveToFile(targetPath, function(stdout, stderr) { 184 | return json_response(res, 'success', { 185 | 'format': 'mp4', 186 | 'path': tempPath 187 | }); 188 | }); 189 | }); 190 | }; 191 | 192 | }).call(this); 193 | -------------------------------------------------------------------------------- /modules/resumable-node.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), path = require('path'), util = require('util'), Stream = require('stream').Stream; 2 | 3 | 4 | 5 | module.exports = resumable = function(temporaryFolder){ 6 | var $ = this; 7 | $.temporaryFolder = temporaryFolder; 8 | $.maxFileSize = null; 9 | $.fileParameterName = 'file'; 10 | 11 | try { 12 | fs.mkdirSync($.temporaryFolder); 13 | }catch(e){} 14 | 15 | 16 | var cleanIdentifier = function(identifier){ 17 | return identifier.replace(/^0-9A-Za-z_-/img, ''); 18 | } 19 | 20 | var getChunkFilename = function(chunkNumber, identifier){ 21 | // Clean up the identifier 22 | identifier = cleanIdentifier(identifier); 23 | // What would the file name be? 24 | return path.join($.temporaryFolder, './resumable-'+identifier+'.'+chunkNumber); 25 | } 26 | 27 | var validateRequest = function(chunkNumber, chunkSize, totalSize, identifier, filename, fileSize){ 28 | // Clean up the identifier 29 | identifier = cleanIdentifier(identifier); 30 | 31 | // Check if the request is sane 32 | if (chunkNumber==0 || chunkSize==0 || totalSize==0 || identifier.length==0 || filename.length==0) { 33 | return 'non_resumable_request'; 34 | } 35 | var numberOfChunks = Math.max(Math.floor(totalSize/(chunkSize*1.0)), 1); 36 | if (chunkNumber>numberOfChunks) { 37 | return 'invalid_resumable_request1'; 38 | } 39 | 40 | // Is the file too big? 41 | if($.maxFileSize && totalSize>$.maxFileSize) { 42 | return 'invalid_resumable_request2'; 43 | } 44 | 45 | if(typeof(fileSize)!='undefined') { 46 | if(chunkNumber1 && chunkNumber==numberOfChunks && fileSize!=((totalSize%chunkSize)+chunkSize)) { 51 | // The chunks in the POST is the last one, and the fil is not the correct size 52 | return 'invalid_resumable_request4'; 53 | } 54 | if(numberOfChunks==1 && fileSize!=totalSize) { 55 | // The file is only a single chunk, and the data size does not fit 56 | return 'invalid_resumable_request5'; 57 | } 58 | } 59 | 60 | return 'valid'; 61 | } 62 | 63 | //'found', filename, original_filename, identifier 64 | //'not_found', null, null, null 65 | $.get = function(req, callback){ 66 | var chunkNumber = req.param('resumableChunkNumber', 0); 67 | var chunkSize = req.param('resumableChunkSize', 0); 68 | var totalSize = req.param('resumableTotalSize', 0); 69 | var identifier = req.param('resumableIdentifier', ""); 70 | var filename = req.param('resumableFilename', ""); 71 | 72 | if(validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename)=='valid') { 73 | var chunkFilename = getChunkFilename(chunkNumber, identifier); 74 | fs.exists(chunkFilename, function(exists){ 75 | if(exists){ 76 | callback('found', chunkFilename, filename, identifier); 77 | } else { 78 | callback('not_found', null, null, null); 79 | } 80 | }); 81 | } else { 82 | callback('not_found', null, null, null); 83 | } 84 | } 85 | 86 | //'partly_done', filename, original_filename, identifier 87 | //'done', filename, original_filename, identifier 88 | //'invalid_resumable_request', null, null, null 89 | //'non_resumable_request', null, null, null 90 | $.post = function(req, callback){ 91 | 92 | var fields = req.body; 93 | var files = req.files; 94 | 95 | var chunkNumber = fields['resumableChunkNumber']; 96 | var chunkSize = fields['resumableChunkSize']; 97 | var totalSize = fields['resumableTotalSize']; 98 | var identifier = cleanIdentifier(fields['resumableIdentifier']); 99 | var filename = fields['resumableFilename']; 100 | 101 | var original_filename = fields['resumableIdentifier']; 102 | 103 | if(!files[$.fileParameterName] || !files[$.fileParameterName].size) { 104 | callback('invalid_resumable_request', null, null, null); 105 | return; 106 | } 107 | var validation = validateRequest(chunkNumber, chunkSize, totalSize, identifier, files[$.fileParameterName].size); 108 | if(validation=='valid') { 109 | var chunkFilename = getChunkFilename(chunkNumber, identifier); 110 | 111 | // Save the chunk (TODO: OVERWRITE) 112 | fs.rename(files[$.fileParameterName].path, chunkFilename, function(err){ 113 | // Do we have all the chunks? 114 | var currentTestChunk = 1; 115 | var numberOfChunks = Math.max(Math.floor(totalSize/(chunkSize*1.0)), 1); 116 | var testChunkExists = function(){ 117 | fs.exists(getChunkFilename(currentTestChunk, identifier), function(exists){ 118 | if(exists){ 119 | currentTestChunk++; 120 | if(currentTestChunk>numberOfChunks) { 121 | callback('done', filename, original_filename, identifier); 122 | } else { 123 | // Recursion 124 | testChunkExists(); 125 | } 126 | } else { 127 | callback('partly_done', filename, original_filename, identifier); 128 | } 129 | }); 130 | } 131 | testChunkExists(); 132 | }); 133 | } else { 134 | callback(validation, filename, original_filename, identifier); 135 | } 136 | } 137 | 138 | 139 | // Pipe chunks directly in to an existsing WritableStream 140 | // r.write(identifier, response); 141 | // r.write(identifier, response, {end:false}); 142 | // 143 | // var stream = fs.createWriteStream(filename); 144 | // r.write(identifier, stream); 145 | // stream.on('data', function(data){...}); 146 | // stream.on('end', function(){...}); 147 | $.write = function(identifier, writableStream, options) { 148 | options = options || {}; 149 | options.end = (typeof options['end'] == 'undefined' ? true : options['end']); 150 | 151 | // Iterate over each chunk 152 | var pipeChunk = function(number) { 153 | 154 | var chunkFilename = getChunkFilename(number, identifier); 155 | fs.exists(chunkFilename, function(exists) { 156 | 157 | if (exists) { 158 | // If the chunk with the current number exists, 159 | // then create a ReadStream from the file 160 | // and pipe it to the specified writableStream. 161 | var sourceStream = fs.createReadStream(chunkFilename); 162 | sourceStream.pipe(writableStream, { 163 | end: false 164 | }); 165 | sourceStream.on('end', function() { 166 | // When the chunk is fully streamed, 167 | // jump to the next one 168 | pipeChunk(number + 1); 169 | }); 170 | } else { 171 | // When all the chunks have been piped, end the stream 172 | if (options.end) writableStream.end(); 173 | if (options.onDone) options.onDone(); 174 | } 175 | }); 176 | } 177 | pipeChunk(1); 178 | } 179 | 180 | 181 | $.clean = function(identifier, options) { 182 | options = options || {}; 183 | 184 | // Iterate over each chunk 185 | var pipeChunkRm = function(number) { 186 | 187 | var chunkFilename = getChunkFilename(number, identifier); 188 | 189 | //console.log('removing pipeChunkRm ', number, 'chunkFilename', chunkFilename); 190 | fs.exists(chunkFilename, function(exists) { 191 | if (exists) { 192 | 193 | console.log('exist removing ', chunkFilename); 194 | fs.unlink(chunkFilename, function(err) { 195 | if (options.onError) opentions.onError(err); 196 | }); 197 | 198 | pipeChunkRm(number + 1); 199 | 200 | } else { 201 | 202 | if (options.onDone) options.onDone(); 203 | 204 | } 205 | }); 206 | } 207 | pipeChunkRm(1); 208 | } 209 | 210 | return $; 211 | } -------------------------------------------------------------------------------- /public/lib/videojs/font/vjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This is a custom SVG font generated by IcoMoon. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 23 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 56 | 57 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /public/lib/videojs/video-js.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Video.js Default Styles (http://videojs.com) 3 | Version 4.3.0 4 | Create your own skin at http://designer.videojs.com 5 | */.vjs-default-skin{color:#ccc}@font-face{font-family:VideoJS;src:url(font/vjs.eot);src:url(font/vjs.eot?#iefix) format('embedded-opentype'),url(font/vjs.woff) format('woff'),url(font/vjs.ttf) format('truetype');font-weight:400;font-style:normal}.vjs-default-skin .vjs-slider{outline:0;position:relative;cursor:pointer;padding:0;background-color:#333;background-color:rgba(51,51,51,.9)}.vjs-default-skin .vjs-slider:focus{-webkit-box-shadow:0 0 2em #fff;-moz-box-shadow:0 0 2em #fff;box-shadow:0 0 2em #fff}.vjs-default-skin .vjs-slider-handle{position:absolute;left:0;top:0}.vjs-default-skin .vjs-slider-handle:before{content:"\e009";font-family:VideoJS;font-size:1em;line-height:1;text-align:center;text-shadow:0 0 1em #fff;position:absolute;top:0;left:0;-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}.vjs-default-skin .vjs-control-bar{display:none;position:absolute;bottom:0;left:0;right:0;height:3em;background-color:#07141e;background-color:rgba(7,20,30,.7)}.vjs-default-skin.vjs-has-started .vjs-control-bar{display:block;visibility:visible;opacity:1;-webkit-transition:visibility .1s,opacity .1s;-moz-transition:visibility .1s,opacity .1s;-o-transition:visibility .1s,opacity .1s;transition:visibility .1s,opacity .1s}.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar{display:block;visibility:hidden;opacity:0;-webkit-transition:visibility 1s,opacity 1s;-moz-transition:visibility 1s,opacity 1s;-o-transition:visibility 1s,opacity 1s;transition:visibility 1s,opacity 1s}.vjs-default-skin.vjs-controls-disabled .vjs-control-bar{display:none}.vjs-default-skin.vjs-using-native-controls .vjs-control-bar{display:none}@media \0screen{.vjs-default-skin.vjs-user-inactive.vjs-playing .vjs-control-bar :before{content:""}}.vjs-default-skin .vjs-control{outline:0;position:relative;float:left;text-align:center;margin:0;padding:0;height:3em;width:4em}.vjs-default-skin .vjs-control:before{font-family:VideoJS;font-size:1.5em;line-height:2;position:absolute;top:0;left:0;width:100%;height:100%;text-align:center;text-shadow:1px 1px 1px rgba(0,0,0,.5)}.vjs-default-skin .vjs-control:focus:before,.vjs-default-skin .vjs-control:hover:before{text-shadow:0 0 1em #fff}.vjs-default-skin .vjs-control:focus{}.vjs-default-skin .vjs-control-text{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.vjs-default-skin .vjs-play-control{width:5em;cursor:pointer}.vjs-default-skin .vjs-play-control:before{content:"\e001"}.vjs-default-skin.vjs-playing .vjs-play-control:before{content:"\e002"}.vjs-default-skin .vjs-mute-control,.vjs-default-skin .vjs-volume-menu-button{cursor:pointer;float:right}.vjs-default-skin .vjs-mute-control:before,.vjs-default-skin .vjs-volume-menu-button:before{content:"\e006"}.vjs-default-skin .vjs-mute-control.vjs-vol-0:before,.vjs-default-skin .vjs-volume-menu-button.vjs-vol-0:before{content:"\e003"}.vjs-default-skin .vjs-mute-control.vjs-vol-1:before,.vjs-default-skin .vjs-volume-menu-button.vjs-vol-1:before{content:"\e004"}.vjs-default-skin .vjs-mute-control.vjs-vol-2:before,.vjs-default-skin .vjs-volume-menu-button.vjs-vol-2:before{content:"\e005"}.vjs-default-skin .vjs-volume-control{width:5em;float:right}.vjs-default-skin .vjs-volume-bar{width:5em;height:.6em;margin:1.1em auto 0}.vjs-default-skin .vjs-volume-menu-button .vjs-menu-content{height:2.9em}.vjs-default-skin .vjs-volume-level{position:absolute;top:0;left:0;height:.5em;background:#66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat}.vjs-default-skin .vjs-volume-bar .vjs-volume-handle{width:.5em;height:.5em}.vjs-default-skin .vjs-volume-handle:before{font-size:.9em;top:-.2em;left:-.2em;width:1em;height:1em}.vjs-default-skin .vjs-volume-menu-button .vjs-menu .vjs-menu-content{width:6em;left:-4em}.vjs-default-skin .vjs-progress-control{position:absolute;left:0;right:0;width:auto;font-size:.3em;height:1em;top:-1em;-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;transition:all .4s}.vjs-default-skin:hover .vjs-progress-control{font-size:.9em;-webkit-transition:all .2s;-moz-transition:all .2s;-o-transition:all .2s;transition:all .2s}.vjs-default-skin .vjs-progress-holder{height:100%}.vjs-default-skin .vjs-progress-holder .vjs-play-progress,.vjs-default-skin .vjs-progress-holder .vjs-load-progress{position:absolute;display:block;height:100%;margin:0;padding:0;left:0;top:0}.vjs-default-skin .vjs-play-progress{background:#66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat}.vjs-default-skin .vjs-load-progress{background:#646464;background:rgba(255,255,255,.4)}.vjs-default-skin .vjs-seek-handle{width:1.5em;height:100%}.vjs-default-skin .vjs-seek-handle:before{padding-top:.1em}.vjs-default-skin .vjs-time-controls{font-size:1em;line-height:3em}.vjs-default-skin .vjs-current-time{float:left}.vjs-default-skin .vjs-duration{float:left}.vjs-default-skin .vjs-remaining-time{display:none;float:left}.vjs-time-divider{float:left;line-height:3em}.vjs-default-skin .vjs-fullscreen-control{width:3.8em;cursor:pointer;float:right}.vjs-default-skin .vjs-fullscreen-control:before{content:"\e000"}.vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control:before{content:"\e00b"}.vjs-default-skin .vjs-big-play-button{left:.5em;top:.5em;font-size:3em;display:block;z-index:2;position:absolute;width:4em;height:2.6em;text-align:center;vertical-align:middle;cursor:pointer;opacity:1;background-color:#07141e;background-color:rgba(7,20,30,.7);border:.1em solid #3b4249;-webkit-border-radius:.8em;-moz-border-radius:.8em;border-radius:.8em;-webkit-box-shadow:0 0 1em rgba(255,255,255,.25);-moz-box-shadow:0 0 1em rgba(255,255,255,.25);box-shadow:0 0 1em rgba(255,255,255,.25);-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;transition:all .4s}.vjs-default-skin.vjs-big-play-centered .vjs-big-play-button{left:50%;margin-left:-2.1em;top:50%;margin-top:-1.4000000000000001em}.vjs-default-skin.vjs-controls-disabled .vjs-big-play-button{display:none}.vjs-default-skin.vjs-has-started .vjs-big-play-button{display:none}.vjs-default-skin.vjs-using-native-controls .vjs-big-play-button{display:none}.vjs-default-skin:hover .vjs-big-play-button,.vjs-default-skin .vjs-big-play-button:focus{outline:0;border-color:#fff;background-color:#505050;background-color:rgba(50,50,50,.75);-webkit-box-shadow:0 0 3em #fff;-moz-box-shadow:0 0 3em #fff;box-shadow:0 0 3em #fff;-webkit-transition:all 0s;-moz-transition:all 0s;-o-transition:all 0s;transition:all 0s}.vjs-default-skin .vjs-big-play-button:before{content:"\e001";font-family:VideoJS;line-height:2.6em;text-shadow:.05em .05em .1em #000;text-align:center;position:absolute;left:0;width:100%;height:100%}.vjs-loading-spinner{display:none;position:absolute;top:50%;left:50%;font-size:4em;line-height:1;width:1em;height:1em;margin-left:-.5em;margin-top:-.5em;opacity:.75;-webkit-animation:spin 1.5s infinite linear;-moz-animation:spin 1.5s infinite linear;-o-animation:spin 1.5s infinite linear;animation:spin 1.5s infinite linear}.vjs-default-skin .vjs-loading-spinner:before{content:"\e01e";font-family:VideoJS;position:absolute;top:0;left:0;width:1em;height:1em;text-align:center;text-shadow:0 0 .1em #000}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(359deg)}}.vjs-default-skin .vjs-menu-button{float:right;cursor:pointer}.vjs-default-skin .vjs-menu{display:none;position:absolute;bottom:0;left:0;width:0;height:0;margin-bottom:3em;border-left:2em solid transparent;border-right:2em solid transparent;border-top:1.55em solid #000;border-top-color:rgba(7,40,50,.5)}.vjs-default-skin .vjs-menu-button .vjs-menu .vjs-menu-content{display:block;padding:0;margin:0;position:absolute;width:10em;bottom:1.5em;max-height:15em;overflow:auto;left:-5em;background-color:#07141e;background-color:rgba(7,20,30,.7);-webkit-box-shadow:-.2em -.2em .3em rgba(255,255,255,.2);-moz-box-shadow:-.2em -.2em .3em rgba(255,255,255,.2);box-shadow:-.2em -.2em .3em rgba(255,255,255,.2)}.vjs-default-skin .vjs-menu-button:hover .vjs-menu{display:block}.vjs-default-skin .vjs-menu-button ul li{list-style:none;margin:0;padding:.3em 0;line-height:1.4em;font-size:1.2em;text-align:center;text-transform:lowercase}.vjs-default-skin .vjs-menu-button ul li.vjs-selected{background-color:#000}.vjs-default-skin .vjs-menu-button ul li:focus,.vjs-default-skin .vjs-menu-button ul li:hover,.vjs-default-skin .vjs-menu-button ul li.vjs-selected:focus,.vjs-default-skin .vjs-menu-button ul li.vjs-selected:hover{outline:0;color:#111;background-color:#fff;background-color:rgba(255,255,255,.75);-webkit-box-shadow:0 0 1em #fff;-moz-box-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.vjs-default-skin .vjs-menu-button ul li.vjs-menu-title{text-align:center;text-transform:uppercase;font-size:1em;line-height:2em;padding:0;margin:0 0 .3em;font-weight:700;cursor:default}.vjs-default-skin .vjs-subtitles-button:before{content:"\e00c"}.vjs-default-skin .vjs-captions-button:before{content:"\e008"}.vjs-default-skin .vjs-captions-button:focus .vjs-control-content:before,.vjs-default-skin .vjs-captions-button:hover .vjs-control-content:before{-webkit-box-shadow:0 0 1em #fff;-moz-box-shadow:0 0 1em #fff;box-shadow:0 0 1em #fff}.video-js{background-color:#000;position:relative;padding:0;font-size:10px;vertical-align:middle;font-weight:400;font-style:normal;font-family:Arial,sans-serif;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.video-js .vjs-tech{position:absolute;top:0;left:0;width:100%;height:100%}.video-js:-moz-full-screen{position:absolute}body.vjs-full-window{padding:0;margin:0;height:100%;overflow-y:auto}.video-js.vjs-fullscreen{position:fixed;overflow:hidden;z-index:1000;left:0;top:0;bottom:0;right:0;width:100%!important;height:100%!important;_position:absolute}.video-js:-webkit-full-screen{width:100%!important;height:100%!important}.video-js.vjs-fullscreen.vjs-user-inactive{cursor:none}.vjs-poster{background-repeat:no-repeat;background-position:50% 50%;background-size:contain;cursor:pointer;height:100%;margin:0;padding:0;position:relative;width:100%}.vjs-poster img{display:block;margin:0 auto;max-height:100%;padding:0;width:100%}.video-js.vjs-using-native-controls .vjs-poster{display:none}.video-js .vjs-text-track-display{text-align:center;position:absolute;bottom:4em;left:1em;right:1em}.video-js .vjs-text-track{display:none;font-size:1.4em;text-align:center;margin-bottom:.1em;background-color:#000;background-color:rgba(0,0,0,.5)}.video-js .vjs-subtitles{color:#fff}.video-js .vjs-captions{color:#fc6}.vjs-tt-cue{display:block}.vjs-default-skin .vjs-hidden{display:none}.vjs-lock-showing{display:block!important;opacity:1;visibility:visible} -------------------------------------------------------------------------------- /public/lib/bootstrap/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.3 (http://getbootstrap.com) 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | 7 | .btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe0e0e0',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-primary{background-image:-webkit-linear-gradient(top,#428bca 0,#2d6ca2 100%);background-image:linear-gradient(to bottom,#428bca 0,#2d6ca2 100%);background-repeat:repeat-x;border-color:#2b669a;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff2d6ca2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);background-repeat:repeat-x;border-color:#3e8f3e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff419641',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);background-repeat:repeat-x;border-color:#e38d13;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffeb9316',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);background-repeat:repeat-x;border-color:#b92c28;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc12e2a',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);background-repeat:repeat-x;border-color:#28a4c9;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2aabd2',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#357ebd;background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);background-repeat:repeat-x;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff8f8f8',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f3f3f3 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f3f3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff3f3f3',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#222 0,#282828 100%);background-image:linear-gradient(to bottom,#222 0,#282828 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff282828',GradientType=0);-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);background-repeat:repeat-x;border-color:#b2dba1;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffc8e5bc',GradientType=0)}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);background-repeat:repeat-x;border-color:#9acfea;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffb9def0',GradientType=0)}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);background-repeat:repeat-x;border-color:#f5e79e;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fff8efc0',GradientType=0)}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);background-repeat:repeat-x;border-color:#dca7a7;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffe7c3c3',GradientType=0)}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb',endColorstr='#fff5f5f5',GradientType=0)}.progress-bar{background-image:-webkit-linear-gradient(top,#428bca 0,#3071a9 100%);background-image:linear-gradient(to bottom,#428bca 0,#3071a9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3071a9',GradientType=0)}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c',endColorstr='#ff449d44',GradientType=0)}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff31b0d5',GradientType=0)}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e',endColorstr='#ffec971f',GradientType=0)}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f',endColorstr='#ffc9302c',GradientType=0)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top,#428bca 0,#3278b3 100%);background-image:linear-gradient(to bottom,#428bca 0,#3278b3 100%);background-repeat:repeat-x;border-color:#3278b3;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff3278b3',GradientType=0)}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#ffe8e8e8',GradientType=0)}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#428bca 0,#357ebd 100%);background-image:linear-gradient(to bottom,#428bca 0,#357ebd 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca',endColorstr='#ff357ebd',GradientType=0)}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8',endColorstr='#ffd0e9c6',GradientType=0)}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7',endColorstr='#ffc4e3f3',GradientType=0)}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3',endColorstr='#fffaf2cc',GradientType=0)}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede',endColorstr='#ffebcccc',GradientType=0)}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);background-repeat:repeat-x;border-color:#dcdcdc;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)} -------------------------------------------------------------------------------- /public/lib/bootstrap/css/bootstrap-theme.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.0.3 (http://getbootstrap.com) 3 | * Copyright 2013 Twitter, Inc. 4 | * Licensed under http://www.apache.org/licenses/LICENSE-2.0 5 | */ 6 | 7 | .btn-default, 8 | .btn-primary, 9 | .btn-success, 10 | .btn-info, 11 | .btn-warning, 12 | .btn-danger { 13 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2); 14 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 15 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); 16 | } 17 | 18 | .btn-default:active, 19 | .btn-primary:active, 20 | .btn-success:active, 21 | .btn-info:active, 22 | .btn-warning:active, 23 | .btn-danger:active, 24 | .btn-default.active, 25 | .btn-primary.active, 26 | .btn-success.active, 27 | .btn-info.active, 28 | .btn-warning.active, 29 | .btn-danger.active { 30 | -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 31 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 32 | } 33 | 34 | .btn:active, 35 | .btn.active { 36 | background-image: none; 37 | } 38 | 39 | .btn-default { 40 | text-shadow: 0 1px 0 #fff; 41 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%); 42 | background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%); 43 | background-repeat: repeat-x; 44 | border-color: #dbdbdb; 45 | border-color: #ccc; 46 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); 47 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 48 | } 49 | 50 | .btn-default:hover, 51 | .btn-default:focus { 52 | background-color: #e0e0e0; 53 | background-position: 0 -15px; 54 | } 55 | 56 | .btn-default:active, 57 | .btn-default.active { 58 | background-color: #e0e0e0; 59 | border-color: #dbdbdb; 60 | } 61 | 62 | .btn-primary { 63 | background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); 64 | background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); 65 | background-repeat: repeat-x; 66 | border-color: #2b669a; 67 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); 68 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 69 | } 70 | 71 | .btn-primary:hover, 72 | .btn-primary:focus { 73 | background-color: #2d6ca2; 74 | background-position: 0 -15px; 75 | } 76 | 77 | .btn-primary:active, 78 | .btn-primary.active { 79 | background-color: #2d6ca2; 80 | border-color: #2b669a; 81 | } 82 | 83 | .btn-success { 84 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); 85 | background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); 86 | background-repeat: repeat-x; 87 | border-color: #3e8f3e; 88 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); 89 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 90 | } 91 | 92 | .btn-success:hover, 93 | .btn-success:focus { 94 | background-color: #419641; 95 | background-position: 0 -15px; 96 | } 97 | 98 | .btn-success:active, 99 | .btn-success.active { 100 | background-color: #419641; 101 | border-color: #3e8f3e; 102 | } 103 | 104 | .btn-warning { 105 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); 106 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); 107 | background-repeat: repeat-x; 108 | border-color: #e38d13; 109 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); 110 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 111 | } 112 | 113 | .btn-warning:hover, 114 | .btn-warning:focus { 115 | background-color: #eb9316; 116 | background-position: 0 -15px; 117 | } 118 | 119 | .btn-warning:active, 120 | .btn-warning.active { 121 | background-color: #eb9316; 122 | border-color: #e38d13; 123 | } 124 | 125 | .btn-danger { 126 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); 127 | background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); 128 | background-repeat: repeat-x; 129 | border-color: #b92c28; 130 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); 131 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 132 | } 133 | 134 | .btn-danger:hover, 135 | .btn-danger:focus { 136 | background-color: #c12e2a; 137 | background-position: 0 -15px; 138 | } 139 | 140 | .btn-danger:active, 141 | .btn-danger.active { 142 | background-color: #c12e2a; 143 | border-color: #b92c28; 144 | } 145 | 146 | .btn-info { 147 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); 148 | background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); 149 | background-repeat: repeat-x; 150 | border-color: #28a4c9; 151 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); 152 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 153 | } 154 | 155 | .btn-info:hover, 156 | .btn-info:focus { 157 | background-color: #2aabd2; 158 | background-position: 0 -15px; 159 | } 160 | 161 | .btn-info:active, 162 | .btn-info.active { 163 | background-color: #2aabd2; 164 | border-color: #28a4c9; 165 | } 166 | 167 | .thumbnail, 168 | .img-thumbnail { 169 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 170 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 171 | } 172 | 173 | .dropdown-menu > li > a:hover, 174 | .dropdown-menu > li > a:focus { 175 | background-color: #e8e8e8; 176 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 177 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 178 | background-repeat: repeat-x; 179 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 180 | } 181 | 182 | .dropdown-menu > .active > a, 183 | .dropdown-menu > .active > a:hover, 184 | .dropdown-menu > .active > a:focus { 185 | background-color: #357ebd; 186 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 187 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 188 | background-repeat: repeat-x; 189 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 190 | } 191 | 192 | .navbar-default { 193 | background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%); 194 | background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%); 195 | background-repeat: repeat-x; 196 | border-radius: 4px; 197 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); 198 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 199 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 200 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075); 201 | } 202 | 203 | .navbar-default .navbar-nav > .active > a { 204 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); 205 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); 206 | background-repeat: repeat-x; 207 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); 208 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 209 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075); 210 | } 211 | 212 | .navbar-brand, 213 | .navbar-nav > li > a { 214 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25); 215 | } 216 | 217 | .navbar-inverse { 218 | background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%); 219 | background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%); 220 | background-repeat: repeat-x; 221 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); 222 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 223 | } 224 | 225 | .navbar-inverse .navbar-nav > .active > a { 226 | background-image: -webkit-linear-gradient(top, #222222 0%, #282828 100%); 227 | background-image: linear-gradient(to bottom, #222222 0%, #282828 100%); 228 | background-repeat: repeat-x; 229 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); 230 | -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 231 | box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25); 232 | } 233 | 234 | .navbar-inverse .navbar-brand, 235 | .navbar-inverse .navbar-nav > li > a { 236 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 237 | } 238 | 239 | .navbar-static-top, 240 | .navbar-fixed-top, 241 | .navbar-fixed-bottom { 242 | border-radius: 0; 243 | } 244 | 245 | .alert { 246 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2); 247 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 248 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05); 249 | } 250 | 251 | .alert-success { 252 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); 253 | background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); 254 | background-repeat: repeat-x; 255 | border-color: #b2dba1; 256 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); 257 | } 258 | 259 | .alert-info { 260 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); 261 | background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); 262 | background-repeat: repeat-x; 263 | border-color: #9acfea; 264 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); 265 | } 266 | 267 | .alert-warning { 268 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); 269 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); 270 | background-repeat: repeat-x; 271 | border-color: #f5e79e; 272 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); 273 | } 274 | 275 | .alert-danger { 276 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); 277 | background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); 278 | background-repeat: repeat-x; 279 | border-color: #dca7a7; 280 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); 281 | } 282 | 283 | .progress { 284 | background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); 285 | background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); 286 | background-repeat: repeat-x; 287 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); 288 | } 289 | 290 | .progress-bar { 291 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); 292 | background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); 293 | background-repeat: repeat-x; 294 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); 295 | } 296 | 297 | .progress-bar-success { 298 | background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); 299 | background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); 300 | background-repeat: repeat-x; 301 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); 302 | } 303 | 304 | .progress-bar-info { 305 | background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); 306 | background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); 307 | background-repeat: repeat-x; 308 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); 309 | } 310 | 311 | .progress-bar-warning { 312 | background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); 313 | background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); 314 | background-repeat: repeat-x; 315 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); 316 | } 317 | 318 | .progress-bar-danger { 319 | background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); 320 | background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); 321 | background-repeat: repeat-x; 322 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); 323 | } 324 | 325 | .list-group { 326 | border-radius: 4px; 327 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 328 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075); 329 | } 330 | 331 | .list-group-item.active, 332 | .list-group-item.active:hover, 333 | .list-group-item.active:focus { 334 | text-shadow: 0 -1px 0 #3071a9; 335 | background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); 336 | background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); 337 | background-repeat: repeat-x; 338 | border-color: #3278b3; 339 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); 340 | } 341 | 342 | .panel { 343 | -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 344 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); 345 | } 346 | 347 | .panel-default > .panel-heading { 348 | background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); 349 | background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); 350 | background-repeat: repeat-x; 351 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); 352 | } 353 | 354 | .panel-primary > .panel-heading { 355 | background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); 356 | background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); 357 | background-repeat: repeat-x; 358 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); 359 | } 360 | 361 | .panel-success > .panel-heading { 362 | background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); 363 | background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); 364 | background-repeat: repeat-x; 365 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); 366 | } 367 | 368 | .panel-info > .panel-heading { 369 | background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); 370 | background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); 371 | background-repeat: repeat-x; 372 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); 373 | } 374 | 375 | .panel-warning > .panel-heading { 376 | background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); 377 | background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); 378 | background-repeat: repeat-x; 379 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); 380 | } 381 | 382 | .panel-danger > .panel-heading { 383 | background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); 384 | background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); 385 | background-repeat: repeat-x; 386 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); 387 | } 388 | 389 | .well { 390 | background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); 391 | background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); 392 | background-repeat: repeat-x; 393 | border-color: #dcdcdc; 394 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); 395 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 396 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1); 397 | } -------------------------------------------------------------------------------- /public/lib/videojs/video-js.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Video.js Default Styles (http://videojs.com) 3 | Version 4.3.0 4 | Create your own skin at http://designer.videojs.com 5 | */ 6 | /* SKIN 7 | ================================================================================ 8 | The main class name for all skin-specific styles. To make your own skin, 9 | replace all occurances of 'vjs-default-skin' with a new name. Then add your new 10 | skin name to your video tag instead of the default skin. 11 | e.g.