Skip to content

Commit

Permalink
Merge pull request #57 from OwenRay/feature/playqueue
Browse files Browse the repository at this point in the history
Feature/playqueue
  • Loading branch information
OwenRay authored Sep 18, 2019
2 parents 202722e + 3b27059 commit e9d46e6
Show file tree
Hide file tree
Showing 60 changed files with 1,188 additions and 348 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ ffmpeg.exe
ffprobe.exe
*.iml
data/
*.rbw
*.tgz
dist
platform_scripts/nasos/source/rms
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ db
ffprobe
ffmpeg.exe
ffprobe.exe
platform_scripts
*.rbw
*.tgz
dist
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
### OPTION 1, INSTALL PRECOMPILED
Download and unzip the version appropriate to your os.

Linux | Windows | MacOS | Raspberry PI
--- | --- | --- | ---
[![Linux](doc/linux.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/linux.zip) | [![Windows](doc/windows.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/win.zip) | [![MacOS](doc/macos.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/osx.zip) | [![MacOS](doc/pi.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/arm.zip)
Linux | Windows | MacOS | Raspberry PI | Docker
--- | --- | --- | --- | ---
[![Linux](doc/linux.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/linux.zip) | [![Windows](doc/windows.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/win.zip) | [![MacOS](doc/macos.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/osx.zip) | [![Mac](doc/pi.png)](https://s3-eu-west-1.amazonaws.com/remote-mediaserver/dev/arm.zip) | [![Docker](doc/docker.png)](https://hub.docker.com/r/owenray/remote-mediaserver)

### OPTION 2, INSTALL VIA NPM
You'll need:
Expand Down
2 changes: 1 addition & 1 deletion backend/core/database/DatabaseSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DatabaseSearch {
// eslint-disable-next-line guard-for-in,no-restricted-syntax
for (const key in sortArray) {
const sortItem = sortArray[key][0];
let direction = sortItem.length > 1 ? sortItem[1] : 'ASC';
let direction = sortArray[key].length > 1 ? sortArray[key][1] : 'ASC';
direction = direction === 'ASC' ? 1 : -1;
if (a.attributes[sortItem] === undefined || a.attributes[sortItem] === null) {
return 1;
Expand Down
31 changes: 22 additions & 9 deletions backend/modules/ffmpeg/FFMpeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@ const { spawn } = require('child_process');
const os = require('os');
const fs = require('fs');

const supportedVideoCodecs = { h264: 1 };
const supportedAudioCodecs = { aac: 1 };

class FFMpeg {
constructor(mediaItem, output) {
constructor(mediaItem, output, profile) {
this.mediaItem = mediaItem;
this.file = MediaItemHelper.getFullFilePath(mediaItem);
this.output = output;
this.outputArgs = [];
this.inputArgs = [];
this.vCodec = 'libx264';
this.aCodec = 'aac';
this.vCodec = null;
this.aCodec = null;
this.setProfile(profile);
}

run() {
Expand All @@ -37,6 +35,15 @@ class FFMpeg {
return this;
}

setProfile(profile) {
this.profile = profile;
const { ffmpegArguments: { output, input } } = profile;
if (output) this.addOutputArguments(output);
if (input) this.addInputArguments(input);

return this;
}

/**
*
* @param args array
Expand Down Expand Up @@ -89,21 +96,27 @@ class FFMpeg {


gotInfo(info) {
const { demux, encoder } = this.profile;
if (!this.vCodec) this.vCodec = encoder.video;
if (!this.aCodec) this.aCodec = encoder.audio;

info.streams.forEach((stream) => {
if (stream.codec_type === 'video') {
if (this.videoChannel === undefined) {
this.videoChannel = stream.index;
}
if (`${this.videoChannel}` === `${stream.index}` && supportedVideoCodecs[stream.codec_name]) {
if (`${this.videoChannel}` === `${stream.index}`
&& demux.video.indexOf(stream.codec_name) !== -1) {
this.vCodec = 'copy';
}
}
if (stream.codec_type === 'audio') {
if (this.audioChannel === undefined) {
this.audioChannel = stream.index;
}
if (`${this.audioChannel}` === `${stream.index}` && supportedAudioCodecs[stream.codec_name]) {
this.aCodec = 'aac';
if (`${this.audioChannel}` === `${stream.index}`
&& demux.audio.indexOf(stream.codec_name) !== -1) {
this.aCodec = 'copy';
}
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,30 @@ const Log = require('../../core/Log');
const RequestHandler = require('../../core/http/RequestHandler');
const FileRequestHandler = require('../../core/http/coreHandlers/FileRequestHandler');
const Database = require('../../core/database/Database');
const httpServer = require('../../core/http');
const FFMpeg = require('./FFMpeg');

class HLSPlayHandler extends RequestHandler {
handleRequest() {
class HLSContainer extends RequestHandler {
handleRequest(profile) {
const { params, query } = this.context;
const mediaItem = Database.getById('media-item', params.id);

if (query.format !== 'hls' &&
(this.request.headers['user-agent'].indexOf('Chrome') !== -1 ||
(
this.request.headers['user-agent'].indexOf('Safari') === -1 &&
this.request.headers['user-agent'].indexOf('AppleCoreMedia') === -1
)
)) {
return false;
}

if (this.request.headers['x-playback-session-id'] && !query.session) {
this.context.query.session = this.request.headers['x-playback-session-id'];
}

if (query.session && HLSPlayHandler.sessions[query.session]) {
return HLSPlayHandler.sessions[query.session]
if (query.session && HLSContainer.sessions[query.session]) {
return HLSContainer.sessions[query.session]
.newRequest(this.context, query.segment);
}

Log.debug('STARTING NEW HLS SESSION!!!');
const id = query.session ? query.session : uuid.v4();
this.session = id;
const redirectUrl = `/ply/${mediaItem.id}/0?format=hls&session=${id}`;
const redirectUrl = `/ply/${mediaItem.id}/0?profile=${profile.name}&format=hls&session=${id}`;
this.profile = profile;
Log.debug('started hls session', redirectUrl);

HLSPlayHandler.sessions[id] = this;
HLSContainer.sessions[id] = this;
this.setSessionTimeout();

// prepare for decoding
Expand All @@ -58,13 +48,12 @@ class HLSPlayHandler extends RequestHandler {
}
this.m3u8 = `${dir}vid.m3u8`;

this.ffmpeg = new FFMpeg(mediaItem, this.m3u8)
const [baseUrl] = this.request.url.split('?');
this.ffmpeg = new FFMpeg(mediaItem, this.m3u8, profile)
.setPlayOffset(params.offset)
.addOutputArguments([
'-hls_time', 10,
'-hls_list_size', 0,
'-hls_base_url', `${this.request.url.split('?')[0]}?format=hls&session=${this.session}&segment=`,
'-bsf:v', 'h264_mp4toannexb',
'-hls_base_url',
`${baseUrl}?profile=${profile.name}&format=hls&session=${this.session}&segment=`,
])
.setOnClose(this.onClose.bind(this))
.setOnReadyListener(this.onReady.bind(this))
Expand Down Expand Up @@ -132,7 +121,7 @@ class HLSPlayHandler extends RequestHandler {
.serveFile(file, true, resolve);
}

if (!this.ffmpeg.paused && !this.playStart) {
if (!this.profile.neverPause && !this.ffmpeg.paused && !this.playStart) {
setTimeout(() => {
this.newRequest(context, segment).then(resolve);
}, 1000);
Expand Down Expand Up @@ -190,6 +179,7 @@ class HLSPlayHandler extends RequestHandler {
}

onReady() {
if (this.profile.neverPause) return;
this.checkPause();
this.checkPauseInterval = setInterval(this.checkPause.bind(this), 100);
}
Expand All @@ -203,7 +193,7 @@ class HLSPlayHandler extends RequestHandler {
if (err) {
return;
}
const limit = 6;
const limit = this.profile.maxBufferedChunks;
if (files.length > limit && !this.ffmpeg.paused) {
this.ffmpeg.pause();
} else if (files.length <= limit && this.ffmpeg.paused) {
Expand All @@ -217,9 +207,5 @@ class HLSPlayHandler extends RequestHandler {
}
}

HLSPlayHandler.sessions = [];

httpServer.registerRoute('get', '/ply/:id', HLSPlayHandler, false, 5);
httpServer.registerRoute('get', '/ply/:id/:offset', HLSPlayHandler, false, 5);

module.exports = HLSPlayHandler;
HLSContainer.sessions = [];
module.exports = HLSContainer;
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,21 @@

const FFMpeg = require('./FFMpeg');
const Database = require('../../core/database/Database');
const httpServer = require('../../core/http');

const Log = require('../../core/Log');
const RequestHandler = require('../../core/http/RequestHandler');
const { PassThrough } = require('stream');

class Mpeg4PlayHandler extends RequestHandler {
handleRequest() {
class Mpeg4Container extends RequestHandler {
handleRequest(profile) {
const mediaItem = Database.getById('media-item', this.context.params.id);
this.context.set('Accept-Ranges', 'none');
this.context.set('Access-Control-Allow-Origin', '*');
this.ffmpeg = new FFMpeg(mediaItem, '-')
this.ffmpeg = new FFMpeg(mediaItem, '-', profile)
.setPlayOffset(this.context.params.offset)
.setOnReadyListener(this.onFFMpegReady.bind(this))
.addOutputArguments([
'-f', 'mp4',
'-movflags', 'empty_moov+omit_tfhd_offset+default_base_moof+frag_keyframe',
'-reset_timestamps', '1',
]);
if (this.context.query.audioChannel) {
this.ffmpeg.setAudioChannel(this.context.query.audioChannel);
Expand Down Expand Up @@ -90,7 +87,4 @@ class Mpeg4PlayHandler extends RequestHandler {
}
}

httpServer.registerRoute('get', '/ply/:id', Mpeg4PlayHandler, false, 0);
httpServer.registerRoute('get', '/ply/:id/:offset', Mpeg4PlayHandler, false, 0);

module.exports = Mpeg4PlayHandler;
module.exports = Mpeg4Container;
41 changes: 41 additions & 0 deletions backend/modules/ffmpeg/PlayHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const httpServer = require('../../core/http');
const RequestHandler = require('../../core/http/RequestHandler');
const fs = require('fs');
const Mpeg4Container = require('./Mpeg4Container.js');
const HLSContainer = require('./HLSContainer.js');

const containers = {
mpeg4: Mpeg4Container,
hls: HLSContainer,
};

// read all the profiles, parse their json and add remember the filename for reference
const profileDir = `${__dirname}/profiles/`;
const profiles = fs.readdirSync(profileDir)
.map(filename => ({
name: filename.split('.')[0],
...(JSON.parse(fs.readFileSync(`${profileDir}${filename}`))),
}))
.sort((a, b) => b.priority - a.priority);

class PlayHandler extends RequestHandler {
handleRequest() {
const { query } = this.context;

const ua = this.request.headers['user-agent'];
let profile;
if (query.profile) {
profile = profiles.find(p => p.name === query.profile);
}
if (!profile) {
profile = profiles.find(p => p.useragent && ua.match(new RegExp(p.useragent)));
}

const Container = containers[profile.container];
const container = new Container(this.context, this.method, this.path);
return container.handleRequest(profile);
}
}

httpServer.registerRoute('get', '/ply/:id', PlayHandler, false, 0);
httpServer.registerRoute('get', '/ply/:id/:offset', PlayHandler, false, 0);
3 changes: 1 addition & 2 deletions backend/modules/ffmpeg/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,5 @@ if (os.platform() === 'win32') {

require('./FFProbeExtendedInfo');
require('./FFProbeImageHandler');
require('./HLSPlayHandler');
require('./MediaContentApiHandler');
require('./Mpeg4PlayHandler');
require('./PlayHandler');
26 changes: 26 additions & 0 deletions backend/modules/ffmpeg/profiles/chromecast_ultra.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"useragent": "^.*aarch64.*Chrome.*CrKey.*$",
"priority": 1,
"container": "mpeg4",
"encoder": {
"audio": "aac",
"video": "libx264"
},
"demux": {
"video": [
"h264",
"hevc"
],
"audio": [
]
},
"ffmpegArguments": {
"output": [
"-movflags", "empty_moov+omit_tfhd_offset+default_base_moof+frag_keyframe",
"-reset_timestamps", "1"
],
"input": [

]
}
}
25 changes: 25 additions & 0 deletions backend/modules/ffmpeg/profiles/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"useragent": "^.*$",
"priority": 0,
"container": "mpeg4",
"encoder": {
"audio": "aac",
"video": "libx264"
},
"demux": {
"video": [
"h264"
],
"audio": [
]
},
"ffmpegArguments": {
"output": [
"-movflags", "empty_moov+omit_tfhd_offset+default_base_moof+frag_keyframe",
"-reset_timestamps", "1"
],
"input": [

]
}
}
26 changes: 26 additions & 0 deletions backend/modules/ffmpeg/profiles/hls_for_offline_playback.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"priority": 0,
"container": "hls",
"neverPause": true,
"encoder": {
"audio": "aac",
"video": "libx264"
},
"demux": {
"video": [
"h264"
],
"audio": [
]
},
"ffmpegArguments": {
"output": [
"-hls_time", 30,
"-hls_list_size", 0,
"-bsf:v", "h264_mp4toannexb"
],
"input": [

]
}
}
Loading

0 comments on commit e9d46e6

Please sign in to comment.