113 lines
4.4 KiB
JavaScript
113 lines
4.4 KiB
JavaScript
/******************
|
|
* This module is to take a "program" and return a stream that plays the
|
|
* program. OR the promise fails which would mean that there was an error
|
|
* playing the program.
|
|
*
|
|
* The main purpose is to have an abstract interface for playing program
|
|
* objects without having to worry the source of the program object.
|
|
* A long-term goal is to be able to have sources other than plex to play
|
|
* videos. This is the first step towards that goal.
|
|
*
|
|
* Returns an event emitter that will have the 'data' or 'end' events.
|
|
* The contract is that the emitter will stream at least some media stream
|
|
* before ending. Any errors that occur after sending the first data will
|
|
* be dealt with internally and be presented as an 'end' event.
|
|
*
|
|
* If there is a timeout when receiving the initial data, or if the program
|
|
* can't load at all for some reason, an Error will be thrown. Make sure to
|
|
* deal with the thrown error.
|
|
**/
|
|
|
|
let OfflinePlayer = require('./offline-player');
|
|
let PlexPlayer = require('./plex-player');;
|
|
const EventEmitter = require('events');
|
|
const helperFuncs = require('./helperFuncs');
|
|
|
|
|
|
class ProgramPlayer {
|
|
|
|
constructor( context ) {
|
|
this.context = context;
|
|
let program = context.lineupItem;
|
|
if (context.m3u8) {
|
|
context.ffmpegSettings.normalizeAudio = false;
|
|
// people might want the codec normalization to stay because of player support
|
|
context.ffmpegSettings.normalizeResolution = false;
|
|
}
|
|
if ( typeof(program.err) !== 'undefined') {
|
|
console.log("About to play error stream");
|
|
this.delegate = new OfflinePlayer(true, context);
|
|
} else if (program.type === 'loading') {
|
|
console.log("About to play loading stream");
|
|
/* loading */
|
|
context.isLoading = true;
|
|
this.delegate = new OfflinePlayer(false, context);
|
|
} else if (program.type === 'offline') {
|
|
console.log("About to play offline stream");
|
|
/* offline */
|
|
this.delegate = new OfflinePlayer(false, context);
|
|
} else {
|
|
console.log("About to play plex stream");
|
|
/* plex */
|
|
this.delegate = new PlexPlayer(context);
|
|
}
|
|
this.context.watermark = helperFuncs.getWatermark( context.ffmpegSettings, context.channel, context.lineupItem.type);
|
|
}
|
|
|
|
cleanUp() {
|
|
this.delegate.cleanUp();
|
|
}
|
|
|
|
async playDelegate(outStream) {
|
|
return await new Promise( async (accept, reject) => {
|
|
|
|
try {
|
|
let stream = await this.delegate.play(outStream);
|
|
accept(stream);
|
|
let emitter = new EventEmitter();
|
|
function end() {
|
|
reject( Error("Stream ended with no data") );
|
|
stream.removeAllListeners("data");
|
|
stream.removeAllListeners("end");
|
|
stream.removeAllListeners("close");
|
|
stream.removeAllListeners("error");
|
|
emitter.emit("end");
|
|
}
|
|
stream.on("error", err => {
|
|
reject( Error("Stream ended in error with no data. " + JSON.stringify(err) ) );
|
|
end();
|
|
});
|
|
stream.on("end", end);
|
|
stream.on("close", end);
|
|
} catch (err) {
|
|
reject(err);
|
|
}
|
|
})
|
|
}
|
|
async play(outStream) {
|
|
try {
|
|
return await this.playDelegate(outStream);
|
|
} catch(err) {
|
|
if (! (err instanceof Error) ) {
|
|
err= Error("Program player had an error before receiving any data. " + JSON.stringify(err) );
|
|
}
|
|
if (this.context.lineupItem.err instanceof Error) {
|
|
console.log(err.stack);
|
|
throw Error("Additional error when attempting to play error stream.");
|
|
}
|
|
console.log("Error when attempting to play video. Fallback to error stream: " + err.stack);
|
|
//Retry once with an error stream:
|
|
this.context.lineupItem = {
|
|
err: err,
|
|
start: this.context.lineupItem.start,
|
|
streamDuration: this.context.lineupItem.streamDuration,
|
|
}
|
|
this.delegate.cleanUp();
|
|
this.delegate = new OfflinePlayer(true, this.context);
|
|
return await this.play(outStream);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = ProgramPlayer;
|