├── 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 |
5 | {% for video in videos %}
6 | -
7 |
8 |
9 |
10 |
时长:{{ video.duration }}
11 |
播放次数:{{ video.play_count }}
12 |
弹幕数:{{ video.barrage_count }}
13 |
上传时间:{{ video.time }}
14 |
15 |
16 | {% endfor %}
17 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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.