Add raw video/audio playback with ffmpeg
This commit is contained in:
commit
02771e6623
|
@ -175,3 +175,10 @@ aggressive-gc: false
|
|||
# Allows you to blacklist certain channels. Users will be automatically kicked
|
||||
# upon trying to join one.
|
||||
channel-blacklist: []
|
||||
|
||||
# If you have ffmpeg installed, you can query metadata from raw files, allowing
|
||||
# server-synched raw file playback. This requires the following:
|
||||
# * ffmpeg must be installed on the server
|
||||
# * you must install the fluent-ffmpeg module (npm install fluent-ffmpeg)
|
||||
ffmpeg:
|
||||
enabled: false
|
||||
|
|
|
@ -36,7 +36,8 @@ LibraryModule.prototype.getItem = function (id, cb) {
|
|||
if (err) {
|
||||
cb(err, null);
|
||||
} else {
|
||||
cb(null, new Media(row.id, row.title, row.seconds, row.type, {}));
|
||||
var meta = JSON.parse(row.meta || "{}");
|
||||
cb(null, new Media(row.id, row.title, row.seconds, row.type, meta));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ const DEFAULT_PERMISSIONS = {
|
|||
oplaylistjump: 1.5,
|
||||
oplaylistaddlist: 1.5,
|
||||
playlistaddcustom: 3, // Add custom embed to the playlist
|
||||
playlistaddrawfile: 2, // Add raw file to the playlist
|
||||
playlistaddlive: 1.5, // Add a livestream to the playlist
|
||||
exceedmaxlength: 2, // Add a video longer than the maximum length set
|
||||
addnontemp: 2, // Add a permanent video to the playlist
|
||||
|
@ -195,6 +196,10 @@ PermissionsModule.prototype.canAddCustom = function (account) {
|
|||
return this.hasPermission(account, "playlistaddcustom");
|
||||
};
|
||||
|
||||
PermissionsModule.prototype.canAddRawFile = function (account) {
|
||||
return this.hasPermission(account, "playlistaddrawfile");
|
||||
};
|
||||
|
||||
PermissionsModule.prototype.canMoveVideo = function (account) {
|
||||
return this.hasPermission(account, "playlistmove");
|
||||
};
|
||||
|
|
|
@ -110,9 +110,8 @@ PlaylistModule.prototype.load = function (data) {
|
|||
var i = 0;
|
||||
playlist.pos = parseInt(playlist.pos);
|
||||
playlist.pl.forEach(function (item) {
|
||||
/* Backwards compatibility */
|
||||
var m = new Media(item.media.id, item.media.title, item.media.seconds,
|
||||
item.media.type);
|
||||
item.media.type, item.media.meta || {});
|
||||
var newitem = new PlaylistItem(m, {
|
||||
uid: self._nextuid++,
|
||||
temp: item.temp,
|
||||
|
@ -134,6 +133,7 @@ PlaylistModule.prototype.load = function (data) {
|
|||
|
||||
PlaylistModule.prototype.save = function (data) {
|
||||
var arr = this.items.toArray().map(function (item) {
|
||||
/* Clear Google Docs and Vimeo meta */
|
||||
if (item.media && item.media.meta) {
|
||||
delete item.media.meta.object;
|
||||
delete item.media.meta.params;
|
||||
|
@ -313,8 +313,11 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
|
|||
return;
|
||||
}
|
||||
|
||||
/* Specifying a custom title is currently only allowed for custom media */
|
||||
if (typeof data.title !== "string" || data.type !== "cu") {
|
||||
/**
|
||||
* Specifying a custom title is currently only allowed for custom media
|
||||
* and raw files
|
||||
*/
|
||||
if (typeof data.title !== "string" || (data.type !== "cu" && data.type !== "fi")) {
|
||||
data.title = false;
|
||||
}
|
||||
|
||||
|
@ -348,6 +351,12 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
|
|||
link: link
|
||||
});
|
||||
return;
|
||||
} else if (type === "fi" && !perms.canAddRawFile(user)) {
|
||||
user.socket.emit("queueFail", {
|
||||
msg: "You don't have permission to add raw video files",
|
||||
link: link
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var temp = data.temp || !perms.canAddNonTemp(user);
|
||||
|
@ -397,6 +406,7 @@ PlaylistModule.prototype.handleQueue = function (user, data) {
|
|||
title: data.title,
|
||||
link: link,
|
||||
temp: temp,
|
||||
shouldAddToLibrary: !temp,
|
||||
queueby: queueby,
|
||||
duration: duration,
|
||||
maxlength: maxlength
|
||||
|
@ -430,6 +440,8 @@ PlaylistModule.prototype.queueStandard = function (user, data) {
|
|||
}
|
||||
|
||||
if (item !== null) {
|
||||
/* Don't re-cache data we got from the library */
|
||||
data.shouldAddToLibrary = false;
|
||||
self._addItem(item, data, user, function () {
|
||||
lock.release();
|
||||
self.channel.activeLock.release();
|
||||
|
@ -864,14 +876,33 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
|||
});
|
||||
}
|
||||
|
||||
/* Warn about high bitrate for raw files */
|
||||
if (media.type === "fi" && media.meta.bitrate > 1000) {
|
||||
user.socket.emit("queueWarn", {
|
||||
msg: "This video has a bitrate over 1000kbps. Clients with slow " +
|
||||
"connections may experience lots of buffering."
|
||||
});
|
||||
}
|
||||
|
||||
/* Warn about possibly unsupported formats */
|
||||
if (media.type === "fi" && media.meta.codec.indexOf("/") !== -1 &&
|
||||
media.meta.codec !== "mov/h264" &&
|
||||
media.meta.codec !== "flv/h264") {
|
||||
user.socket.emit("queueWarn", {
|
||||
msg: "The codec <code>" + media.meta.codec + "</code> is not supported " +
|
||||
"by all browsers, and is not supported by the flash fallback layer. " +
|
||||
"This video may not play for some users."
|
||||
});
|
||||
}
|
||||
|
||||
var item = new PlaylistItem(media, {
|
||||
uid: self._nextuid++,
|
||||
temp: data.temp,
|
||||
queueby: data.queueby
|
||||
});
|
||||
|
||||
if (data.title && media.type === "cu") {
|
||||
media.title = data.title;
|
||||
if (data.title && (media.type === "cu" || media.type === "fi")) {
|
||||
media.setTitle(data.title);
|
||||
}
|
||||
|
||||
var success = function () {
|
||||
|
@ -893,7 +924,7 @@ PlaylistModule.prototype._addItem = function (media, data, user, cb) {
|
|||
u.socket.emit("setPlaylistMeta", self.meta);
|
||||
});
|
||||
|
||||
if (!data.temp && !util.isLive(media.type)) {
|
||||
if (data.shouldAddToLibrary && !util.isLive(media.type)) {
|
||||
if (self.channel.modules.library) {
|
||||
self.channel.modules.library.cacheMedia(media);
|
||||
}
|
||||
|
|
|
@ -104,7 +104,10 @@ var defaults = {
|
|||
"max-items": 4000,
|
||||
"update-interval": 5
|
||||
},
|
||||
"channel-blacklist": []
|
||||
"channel-blacklist": [],
|
||||
ffmpeg: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -440,9 +440,14 @@ module.exports = {
|
|||
return;
|
||||
}
|
||||
|
||||
db.query("INSERT INTO `chan_" + chan + "_library` (id, title, seconds, type) " +
|
||||
"VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE id=id",
|
||||
[media.id, media.title, media.seconds, media.type], callback);
|
||||
var meta = JSON.stringify({
|
||||
bitrate: media.meta.bitrate,
|
||||
codec: media.meta.codec
|
||||
});
|
||||
|
||||
db.query("INSERT INTO `chan_" + chan + "_library` (id, title, seconds, type, meta) " +
|
||||
"VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE id=id",
|
||||
[media.id, media.title, media.seconds, media.type, meta], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
var db = require("../database");
|
||||
var Logger = require("../logger");
|
||||
var Q = require("q");
|
||||
|
||||
const DB_VERSION = 1;
|
||||
const DB_VERSION = 2;
|
||||
|
||||
module.exports.checkVersion = function () {
|
||||
db.query("SELECT `key`,`value` FROM `meta` WHERE `key`=?", ["db_version"], function (err, rows) {
|
||||
|
@ -18,6 +19,9 @@ module.exports.checkVersion = function () {
|
|||
});
|
||||
} else {
|
||||
var v = parseInt(rows[0].value);
|
||||
if (v >= DB_VERSION) {
|
||||
return;
|
||||
}
|
||||
var next = function () {
|
||||
if (v < DB_VERSION) {
|
||||
update(v++, next);
|
||||
|
@ -32,5 +36,33 @@ module.exports.checkVersion = function () {
|
|||
};
|
||||
|
||||
function update(version, cb) {
|
||||
setImmediate(cb);
|
||||
if (version === 1) {
|
||||
addMetaColumnToLibraries(cb);
|
||||
}
|
||||
}
|
||||
|
||||
function addMetaColumnToLibraries(cb) {
|
||||
Logger.syslog.log("[database] db version indicates channel libraries don't have " +
|
||||
"meta column. Updating...");
|
||||
Q.nfcall(db.query, "SHOW TABLES")
|
||||
.then(function (rows) {
|
||||
rows = rows.map(function (r) {
|
||||
return r[Object.keys(r)[0]];
|
||||
}).filter(function (r) {
|
||||
return r.match(/_library$/);
|
||||
});
|
||||
|
||||
var queue = [];
|
||||
rows.forEach(function (table) {
|
||||
queue.push(Q.nfcall(db.query, "ALTER TABLE `" + table + "` ADD meta TEXT")
|
||||
.then(function () {
|
||||
Logger.syslog.log("Added meta column to " + table);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return Q.all(queue);
|
||||
}).catch(function (err) {
|
||||
Logger.errlog.log("Adding meta column to library tables failed: " + err);
|
||||
}).done(cb);
|
||||
}
|
||||
|
|
108
lib/ffmpeg.js
Normal file
108
lib/ffmpeg.js
Normal file
|
@ -0,0 +1,108 @@
|
|||
var Logger = require("./logger");
|
||||
var Config = require("./config");
|
||||
var Metadata;
|
||||
var enabled = false;
|
||||
|
||||
function init() {
|
||||
if (Config.get("ffmpeg.enabled")) {
|
||||
try {
|
||||
Metadata = require("fluent-ffmpeg").Metadata;
|
||||
Logger.syslog.log("Enabling raw file support with fluent-ffmpeg");
|
||||
enabled = true;
|
||||
} catch (e) {
|
||||
Logger.errlog.log("Failed to load fluent-ffmpeg. Did you remember to " +
|
||||
"execute `npm install fluent-ffmpeg` ?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var acceptedCodecs = {
|
||||
"mov/h264": true,
|
||||
"flv/h264": true,
|
||||
"matroska/vp8": true,
|
||||
"matroska/vp9": true,
|
||||
"ogg/theora": true
|
||||
};
|
||||
|
||||
var acceptedAudioCodecs = {
|
||||
"mp3": true,
|
||||
"vorbis": true
|
||||
};
|
||||
|
||||
var audioOnlyContainers = {
|
||||
"mp3": true
|
||||
};
|
||||
|
||||
exports.query = function (filename, cb) {
|
||||
if (!Metadata) {
|
||||
init();
|
||||
}
|
||||
|
||||
if (!enabled) {
|
||||
return cb("Raw file playback is not enabled on this server");
|
||||
}
|
||||
|
||||
if (!filename.match(/^https?:\/\//)) {
|
||||
return cb("Raw file playback is only supported for links accessible via HTTP " +
|
||||
"or HTTPS");
|
||||
}
|
||||
|
||||
new Metadata(filename, function (meta, err) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if (isVideo(meta)) {
|
||||
var video = meta.video;
|
||||
var codec = video.container + "/" + video.codec;
|
||||
|
||||
if (!(codec in acceptedCodecs)) {
|
||||
return cb("Unsupported video codec " + codec);
|
||||
}
|
||||
|
||||
var data = {
|
||||
title: meta.title || "Raw Video",
|
||||
duration: Math.ceil(meta.durationsec),
|
||||
bitrate: video.bitrate,
|
||||
codec: codec
|
||||
};
|
||||
|
||||
cb(null, data);
|
||||
} else if (isAudio(meta)) {
|
||||
var audio = meta.audio;
|
||||
var codec = audio.codec;
|
||||
|
||||
if (!(codec in acceptedAudioCodecs)) {
|
||||
return cb("Unsupported audio codec " + codec);
|
||||
}
|
||||
|
||||
var data = {
|
||||
title: meta.title || "Raw Audio",
|
||||
duration: Math.ceil(meta.durationsec),
|
||||
bitrate: audio.bitrate,
|
||||
codec: codec
|
||||
};
|
||||
|
||||
cb(null, data);
|
||||
} else if (data.ffmpegErr.match(/Protocol not found/)) {
|
||||
return cb("This server is unable to load videos over the " +
|
||||
filename.split(":")[0] + " protocol.");
|
||||
} else {
|
||||
return cb("Parsed metadata did not contain a valid video or audio stream. " +
|
||||
"Either the file is invalid or it has a format unsupported by " +
|
||||
"this server's version of ffmpeg.");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function isVideo(meta) {
|
||||
return meta.video &&
|
||||
meta.video.bitrate > 0 &&
|
||||
meta.video.container &&
|
||||
meta.video.codec &&
|
||||
!(meta.video.container in audioOnlyContainers);
|
||||
}
|
||||
|
||||
function isAudio(meta) {
|
||||
return meta.audio && meta.audio.bitrate > 0 && meta.audio.codec;
|
||||
}
|
|
@ -8,7 +8,6 @@ The above copyright notice and this permission notice shall be included in all c
|
|||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
var http = require("http");
|
||||
var https = require("https");
|
||||
var domain = require("domain");
|
||||
|
@ -17,6 +16,7 @@ var Media = require("./media");
|
|||
var CustomEmbedFilter = require("./customembed").filter;
|
||||
var Server = require("./server");
|
||||
var Config = require("./config");
|
||||
var ffmpeg = require("./ffmpeg");
|
||||
|
||||
var urlRetrieve = function (transport, options, callback) {
|
||||
// Catch any errors that crop up along the way of the request
|
||||
|
@ -766,6 +766,21 @@ var Getters = {
|
|||
callback(res, null);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* ffmpeg for raw files */
|
||||
fi: function (id, cb) {
|
||||
ffmpeg.query(id, function (err, data) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
var m = new Media(id, data.title, data.duration, "fi", {
|
||||
bitrate: data.bitrate,
|
||||
codec: data.codec
|
||||
});
|
||||
cb(null, m);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
16
lib/media.js
16
lib/media.js
|
@ -6,10 +6,7 @@ function Media(id, title, seconds, type, meta) {
|
|||
}
|
||||
|
||||
this.id = id;
|
||||
this.title = title;
|
||||
if (this.title.length > 100) {
|
||||
this.title = this.title.substring(0, 97) + "...";
|
||||
}
|
||||
this.setTitle(title);
|
||||
|
||||
this.seconds = seconds === "--:--" ? 0 : parseInt(seconds);
|
||||
this.duration = util.formatTime(seconds);
|
||||
|
@ -20,6 +17,13 @@ function Media(id, title, seconds, type, meta) {
|
|||
}
|
||||
|
||||
Media.prototype = {
|
||||
setTitle: function (title) {
|
||||
this.title = title;
|
||||
if (this.title.length > 100) {
|
||||
this.title = this.title.substring(0, 97) + "...";
|
||||
}
|
||||
},
|
||||
|
||||
pack: function () {
|
||||
return {
|
||||
id: this.id,
|
||||
|
@ -31,7 +35,9 @@ Media.prototype = {
|
|||
object: this.meta.object,
|
||||
params: this.meta.params,
|
||||
direct: this.meta.direct,
|
||||
restricted: this.meta.restricted
|
||||
restricted: this.meta.restricted,
|
||||
codec: this.meta.codec,
|
||||
bitrate: this.meta.bitrate
|
||||
}
|
||||
};
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const VERSION = "3.1.0";
|
||||
const VERSION = "3.2.0";
|
||||
var singleton = null;
|
||||
var Config = require("./config");
|
||||
|
||||
|
|
|
@ -261,6 +261,10 @@
|
|||
return "http://imgur.com/a/" + id;
|
||||
case "us":
|
||||
return "http://ustream.tv/" + id;
|
||||
case "gd":
|
||||
return "https://docs.google.com/file/d/" + id;
|
||||
case "fi":
|
||||
return id;
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"author": "Calvin Montgomery",
|
||||
"name": "CyTube",
|
||||
"description": "Online media synchronizer and chat",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"repository": {
|
||||
"url": "http://github.com/calzoneman/sync"
|
||||
},
|
||||
|
|
|
@ -845,6 +845,14 @@ Callbacks = {
|
|||
$("#ytapiplayer_wrapper").remove();
|
||||
}
|
||||
|
||||
if (data.type === "fi") {
|
||||
if (USEROPTS.no_h264 && data.meta.codec === "mov/h264") {
|
||||
data.forceFlash = true;
|
||||
}
|
||||
|
||||
data.url = data.id;
|
||||
}
|
||||
|
||||
/*
|
||||
VIMEO SIMULATOR 2014
|
||||
|
||||
|
@ -859,11 +867,10 @@ Callbacks = {
|
|||
and unwilling to compromise on the issue.
|
||||
*/
|
||||
if (NO_VIMEO && data.type === "vi" && data.meta.direct) {
|
||||
data.type = "fi";
|
||||
// For browsers that don't support native h264 playback
|
||||
if (USEROPTS.no_h264) {
|
||||
data.type = "fl";
|
||||
} else {
|
||||
data.type = "rv";
|
||||
data.forceFlash = true;
|
||||
}
|
||||
|
||||
/* Convert youtube-style quality key to vimeo workaround quality */
|
||||
|
@ -895,6 +902,8 @@ Callbacks = {
|
|||
|
||||
if (data.type === "rt") {
|
||||
data.url = data.id;
|
||||
data.type = "fi";
|
||||
data.forceFlash = true;
|
||||
}
|
||||
|
||||
if(data.type != PLAYER.type) {
|
||||
|
|
213
www/js/player.js
213
www/js/player.js
|
@ -800,98 +800,6 @@ function flashEventHandler(id, ev, data) {
|
|||
}
|
||||
}
|
||||
|
||||
var FlashPlayer = function (data) {
|
||||
removeOld();
|
||||
var self = this;
|
||||
self.volume = VOLUME;
|
||||
self.videoId = data.id;
|
||||
self.videoUrl = data.url;
|
||||
self.videoLength = data.seconds;
|
||||
self.paused = false;
|
||||
self.currentTime = 0;
|
||||
|
||||
self.init = function () {
|
||||
var params = {
|
||||
allowFullScreen: "true",
|
||||
allowScriptAccess: "always",
|
||||
allowNetworking: "all",
|
||||
wMode: "direct"
|
||||
};
|
||||
|
||||
var flashvars = {
|
||||
src: encodeURIComponent(self.videoUrl),
|
||||
// For some reason this param seems not to work
|
||||
clipStartTime: Math.floor(data.currentTime),
|
||||
javascriptCallbackFunction: "flashEventHandler",
|
||||
autoPlay: true,
|
||||
volume: VOLUME
|
||||
};
|
||||
|
||||
if (self.videoUrl.indexOf("rtmp") === 0) {
|
||||
flashvars.streamType = "live";
|
||||
} else {
|
||||
flashvars.streamType = "recorded";
|
||||
}
|
||||
|
||||
swfobject.embedSWF("/StrobeMediaPlayback.swf",
|
||||
"ytapiplayer",
|
||||
VWIDTH, VHEIGHT,
|
||||
"10.1.0",
|
||||
null,
|
||||
flashvars,
|
||||
params,
|
||||
{ name: "ytapiplayer" }
|
||||
);
|
||||
|
||||
self.player = $("#ytapiplayer")[0];
|
||||
};
|
||||
|
||||
self.load = function (data) {
|
||||
self.videoId = data.id;
|
||||
self.videoUrl = data.url;
|
||||
self.videoLength = data.seconds;
|
||||
self.init();
|
||||
};
|
||||
|
||||
self.pause = function () {
|
||||
if (self.player && self.player.pause)
|
||||
self.player.pause();
|
||||
};
|
||||
|
||||
self.play = function () {
|
||||
// Why is it play2? What happened to play1?
|
||||
if (self.player && self.player.play2)
|
||||
self.player.play2();
|
||||
};
|
||||
|
||||
self.isPaused = function (cb) {
|
||||
cb(self.paused);
|
||||
};
|
||||
|
||||
self.getTime = function (cb) {
|
||||
cb(self.currentTime);
|
||||
};
|
||||
|
||||
self.seek = function (to) {
|
||||
if (self.player && self.player.seek) {
|
||||
self.player.seek(Math.floor(to));
|
||||
}
|
||||
};
|
||||
|
||||
self.getVolume = function (cb) {
|
||||
cb(self.volume);
|
||||
};
|
||||
|
||||
self.setVolume = function (vol) {
|
||||
if (self.player && self.player.setVolume)
|
||||
self.player.setVolume(vol);
|
||||
};
|
||||
|
||||
waitUntilDefined(window, "swfobject", function () {
|
||||
self.init();
|
||||
});
|
||||
};
|
||||
|
||||
var JWPlayer = function (data) {
|
||||
var self = this;
|
||||
self.videoId = data.id;
|
||||
|
@ -1161,12 +1069,100 @@ var GoogleDocsPlayer = function (data) {
|
|||
self.init(data);
|
||||
};
|
||||
|
||||
function RawVideoPlayer(data) {
|
||||
function FilePlayer(data) {
|
||||
var self = this;
|
||||
|
||||
self.initFlash = function (data) {
|
||||
waitUntilDefined(window, "swfobject", function () {
|
||||
self.volume = VOLUME;
|
||||
self.videoId = data.id;
|
||||
self.videoURL = data.url;
|
||||
self.videoLength = data.seconds;
|
||||
self.paused = false;
|
||||
self.currentTime = 0;
|
||||
|
||||
var params = {
|
||||
allowFullScreen: "true",
|
||||
allowScriptAccess: "always",
|
||||
allowNetworking: "all",
|
||||
wMode: "direct"
|
||||
};
|
||||
|
||||
var flashvars = {
|
||||
src: encodeURIComponent(self.videoURL),
|
||||
// For some reason this param seems not to work
|
||||
clipStartTime: Math.floor(data.currentTime),
|
||||
javascriptCallbackFunction: "flashEventHandler",
|
||||
autoPlay: true,
|
||||
volume: VOLUME
|
||||
};
|
||||
|
||||
if (self.videoURL.indexOf("rtmp") === 0) {
|
||||
flashvars.streamType = "live";
|
||||
} else {
|
||||
flashvars.streamType = "recorded";
|
||||
}
|
||||
|
||||
swfobject.embedSWF("/StrobeMediaPlayback.swf",
|
||||
"ytapiplayer",
|
||||
VWIDTH, VHEIGHT,
|
||||
"10.1.0",
|
||||
null,
|
||||
flashvars,
|
||||
params,
|
||||
{ name: "ytapiplayer" }
|
||||
);
|
||||
|
||||
self.player = $("#ytapiplayer")[0];
|
||||
resizeStuff();
|
||||
|
||||
self.pause = function () {
|
||||
if (self.player && self.player.pause)
|
||||
self.player.pause();
|
||||
};
|
||||
|
||||
self.play = function () {
|
||||
// Why is it play2? What happened to play1?
|
||||
if (self.player && self.player.play2)
|
||||
self.player.play2();
|
||||
};
|
||||
|
||||
self.isPaused = function (cb) {
|
||||
cb(self.paused);
|
||||
};
|
||||
|
||||
self.getTime = function (cb) {
|
||||
cb(self.currentTime);
|
||||
};
|
||||
|
||||
self.seek = function (to) {
|
||||
if (self.player && self.player.seek) {
|
||||
self.player.seek(Math.floor(to));
|
||||
}
|
||||
};
|
||||
|
||||
self.getVolume = function (cb) {
|
||||
cb(self.volume);
|
||||
};
|
||||
|
||||
self.setVolume = function (vol) {
|
||||
if (self.player && self.player.setVolume)
|
||||
self.player.setVolume(vol);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
self.init = function (data) {
|
||||
self.videoId = data.id;
|
||||
self.videoURL = data.url;
|
||||
var video = $("<video/>")
|
||||
var isAudio = data.meta.codec && data.meta.codec.match(/^mp3$|^vorbis$/);
|
||||
var video;
|
||||
if (isAudio) {
|
||||
video = $("<audio/>");
|
||||
} else {
|
||||
video = $("<video/>")
|
||||
}
|
||||
video
|
||||
.attr("src", self.videoURL)
|
||||
.attr("controls", "controls")
|
||||
.attr("id", "#ytapiplayer")
|
||||
|
@ -1175,16 +1171,22 @@ function RawVideoPlayer(data) {
|
|||
.html("Your browser does not support HTML5 <code><video></code> tags :(");
|
||||
video.error(function (err) {
|
||||
setTimeout(function () {
|
||||
fallbackRaw(data);
|
||||
console.log("<video> tag failed, falling back to Flash");
|
||||
self.initFlash(data);
|
||||
}, 100);
|
||||
});
|
||||
removeOld(video);
|
||||
self.player = video[0];
|
||||
self.setVolume(VOLUME);
|
||||
resizeStuff();
|
||||
};
|
||||
|
||||
self.load = function (data) {
|
||||
self.init(data);
|
||||
if (data.forceFlash) {
|
||||
self.initFlash(data);
|
||||
} else {
|
||||
self.init(data);
|
||||
}
|
||||
};
|
||||
|
||||
self.pause = function () {
|
||||
|
@ -1213,7 +1215,10 @@ function RawVideoPlayer(data) {
|
|||
|
||||
self.seek = function (time) {
|
||||
if (self.player) {
|
||||
self.player.currentTime = time;
|
||||
try {
|
||||
self.player.currentTime = time;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1233,10 +1238,13 @@ function RawVideoPlayer(data) {
|
|||
}
|
||||
};
|
||||
|
||||
self.init(data);
|
||||
if (data.forceFlash) {
|
||||
self.initFlash(data);
|
||||
} else {
|
||||
self.init(data);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function handleMediaUpdate(data) {
|
||||
// Don't update if the position is past the video length, but
|
||||
// make an exception when the video length is 0 seconds
|
||||
|
@ -1334,13 +1342,14 @@ var constructors = {
|
|||
"tw": TwitchTVPlayer,
|
||||
"jt": JustinTVPlayer,
|
||||
"us": UstreamPlayer,
|
||||
"rt": FlashPlayer,
|
||||
"jw": JWPlayer,
|
||||
"im": ImgurPlayer,
|
||||
"cu": CustomPlayer,
|
||||
"gd": GoogleDocsPlayer,
|
||||
"rv": RawVideoPlayer,
|
||||
"fl": FlashPlayer
|
||||
"rt": FilePlayer,
|
||||
"rv": FilePlayer,
|
||||
"fl": FilePlayer,
|
||||
"fi": FilePlayer
|
||||
};
|
||||
|
||||
function loadMediaPlayer(data) {
|
||||
|
|
59
www/js/ui.js
59
www/js/ui.js
|
@ -340,12 +340,16 @@ function queue(pos, src) {
|
|||
var link = $("#mediaurl").val();
|
||||
var data = parseMediaLink(link);
|
||||
var duration = undefined;
|
||||
var title = undefined;
|
||||
if (link.indexOf("jw:") === 0) {
|
||||
duration = parseInt($("#addfromurl-duration-val").val());
|
||||
if (duration <= 0 || isNaN(duration)) {
|
||||
duration = undefined;
|
||||
}
|
||||
}
|
||||
if (data.type === "fi") {
|
||||
title = $("#addfromurl-title-val").val();
|
||||
}
|
||||
|
||||
if (data.id == null || data.type == null) {
|
||||
makeAlert("Error", "Failed to parse link. Please check that it is correct",
|
||||
|
@ -354,11 +358,13 @@ function queue(pos, src) {
|
|||
} else {
|
||||
$("#mediaurl").val("");
|
||||
$("#addfromurl-duration").remove();
|
||||
$("#addfromurl-title").remove();
|
||||
socket.emit("queue", {
|
||||
id: data.id,
|
||||
type: data.type,
|
||||
pos: pos,
|
||||
duration: duration,
|
||||
title: title,
|
||||
temp: $(".add-temp").prop("checked")
|
||||
});
|
||||
}
|
||||
|
@ -373,21 +379,46 @@ $("#ce_queue_end").click(queue.bind(this, "end", "customembed"));
|
|||
$("#mediaurl").keyup(function(ev) {
|
||||
if (ev.keyCode === 13) {
|
||||
queue("end", "url");
|
||||
} else if ($("#mediaurl").val().indexOf("jw:") === 0) {
|
||||
var duration = $("#addfromurl-duration");
|
||||
if (duration.length === 0) {
|
||||
duration = $("<div/>")
|
||||
.attr("id", "addfromurl-duration")
|
||||
.appendTo($("#addfromurl"));
|
||||
$("<span/>").text("JWPlayer Duration (seconds) (optional)")
|
||||
.appendTo(duration);
|
||||
$("<input/>").addClass("form-control")
|
||||
.attr("type", "text")
|
||||
.attr("id", "addfromurl-duration-val")
|
||||
.appendTo($("#addfromurl-duration"));
|
||||
}
|
||||
} else {
|
||||
$("#addfromurl-duration").remove();
|
||||
if ($("#mediaurl").val().indexOf("jw:") === 0) {
|
||||
var duration = $("#addfromurl-duration");
|
||||
if (duration.length === 0) {
|
||||
duration = $("<div/>")
|
||||
.attr("id", "addfromurl-duration")
|
||||
.appendTo($("#addfromurl"));
|
||||
$("<span/>").text("JWPlayer Duration (seconds) (optional)")
|
||||
.appendTo(duration);
|
||||
$("<input/>").addClass("form-control")
|
||||
.attr("type", "text")
|
||||
.attr("id", "addfromurl-duration-val")
|
||||
.appendTo($("#addfromurl-duration"));
|
||||
}
|
||||
} else {
|
||||
$("#addfromurl-duration").remove();
|
||||
}
|
||||
|
||||
var url = $("#mediaurl").val().split("?")[0];
|
||||
if (url.match(/^https?:\/\/(.*)?\.(flv|mp4|og[gv]|webm|mp3)$/)) {
|
||||
var title = $("#addfromurl-title");
|
||||
if (title.length === 0) {
|
||||
title = $("<div/>")
|
||||
.attr("id", "addfromurl-title")
|
||||
.appendTo($("#addfromurl"));
|
||||
$("<span/>").text("Title (optional)")
|
||||
.appendTo(title);
|
||||
$("<input/>").addClass("form-control")
|
||||
.attr("type", "text")
|
||||
.attr("id", "addfromurl-title-val")
|
||||
.keyup(function (ev) {
|
||||
if (ev.keyCode === 13) {
|
||||
queue("end", "url");
|
||||
}
|
||||
})
|
||||
.appendTo($("#addfromurl-title"));
|
||||
}
|
||||
} else {
|
||||
$("#addfromurl-title").remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -58,6 +58,8 @@ function formatURL(data) {
|
|||
return "http://ustream.tv/" + data.id;
|
||||
case "gd":
|
||||
return "https://docs.google.com/file/d/" + data.id;
|
||||
case "fi":
|
||||
return data.id;
|
||||
default:
|
||||
return "#";
|
||||
}
|
||||
|
@ -1284,6 +1286,24 @@ function parseMediaLink(url) {
|
|||
};
|
||||
}
|
||||
|
||||
/* Raw file */
|
||||
var tmp = url.split("?")[0];
|
||||
if (tmp.match(/^https?:\/\//)) {
|
||||
if (tmp.match(/\.(mp4|flv|webm|og[gv]|mp3)$/)) {
|
||||
return {
|
||||
id: url,
|
||||
type: "fi"
|
||||
};
|
||||
} else {
|
||||
Callbacks.queueFail({
|
||||
link: url,
|
||||
msg: "The file you are attempting to queue does not match the supported " +
|
||||
"file extensions mp4, flv, webm, ogg, ogv, mp3."
|
||||
});
|
||||
throw new Error("ERROR_QUEUE_UNSUPPORTED_EXTENSION");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: null,
|
||||
type: null
|
||||
|
@ -1728,6 +1748,7 @@ function genPermissionsEditor() {
|
|||
makeOption("Queue playlist", "playlistaddlist", standard, CHANNEL.perms.playlistaddlist+"");
|
||||
makeOption("Queue livestream", "playlistaddlive", standard, CHANNEL.perms.playlistaddlive+"");
|
||||
makeOption("Embed custom media", "playlistaddcustom", standard, CHANNEL.perms.playlistaddcustom + "");
|
||||
makeOption("Add raw video file", "playlistaddrawfile", standard, CHANNEL.perms.playlistaddrawfile + "");
|
||||
makeOption("Exceed maximum media length", "exceedmaxlength", standard, CHANNEL.perms.exceedmaxlength+"");
|
||||
makeOption("Add nontemporary media", "addnontemp", standard, CHANNEL.perms.addnontemp+"");
|
||||
makeOption("Temp/untemp playlist item", "settemp", standard, CHANNEL.perms.settemp+"");
|
||||
|
|
Loading…
Reference in a new issue