UNPKG

mirakurun

Version:

DVR Tuner Server for Japanese TV.

354 lines 13.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const child_process = __importStar(require("child_process")); const util = __importStar(require("util")); const EventEmitter = require("eventemitter3"); const common = __importStar(require("./common")); const log = __importStar(require("./log")); const status_1 = __importDefault(require("./status")); const Event_1 = __importDefault(require("./Event")); const client_1 = __importDefault(require("../client")); class TunerDevice extends EventEmitter { _index; _config; _channel = null; _command = null; _process = null; _stream = null; _users = new Set(); _isAvailable = true; _isRemote = false; _isFault = false; _fatalCount = 0; _exited = false; _closing = false; constructor(_index, _config) { super(); this._index = _index; this._config = _config; this._isRemote = !!this._config.remoteMirakurunHost; Event_1.default.emit("tuner", "create", this.toJSON()); log.debug("TunerDevice#%d initialized", this._index); } get index() { return this._index; } get config() { return this._config; } get channel() { return this._channel; } get command() { return this._command; } get pid() { return this._process ? this._process.pid : null; } get users() { return [...this._users].map(user => { return { id: user.id, priority: user.priority, agent: user.agent, url: user.url, disableDecoder: user.disableDecoder, streamSetting: user.streamSetting, streamInfo: user.streamInfo }; }); } get decoder() { return this._config.decoder || null; } get isAvailable() { return this._isAvailable; } get isRemote() { return this._isRemote; } get isFree() { return this._isAvailable === true && this._channel === null && this._users.size === 0; } get isUsing() { return this._isAvailable === true && this._channel !== null && this._users.size !== 0; } get isFault() { return this._isFault; } getPriority() { let priority = -2; for (const user of this._users) { if (user.priority > priority) { priority = user.priority; } } return priority; } toJSON() { return { index: this._index, name: this._config.name, types: this._config.types, command: this._command, pid: this.pid, users: this.users, isAvailable: this.isAvailable, isRemote: this.isRemote, isFree: this.isFree, isUsing: this.isUsing, isFault: this.isFault }; } async kill() { await this._kill(true); } async startStream(user, stream, channel) { log.debug("TunerDevice#%d start stream for user `%s` (priority=%d)...", this._index, user.id, user.priority); if (this._isAvailable === false) { throw new Error(util.format("TunerDevice#%d is not available", this._index)); } if (!channel && !this._stream) { throw new Error(util.format("TunerDevice#%d has not stream", this._index)); } if (channel) { if (this._config.types.includes(channel.type) === false) { throw new Error(util.format("TunerDevice#%d is not supported for channel type `%s`", this._index, channel.type)); } if (this._stream) { if (channel.channel !== this._channel.channel) { if (user.priority <= this.getPriority()) { throw new Error(util.format("TunerDevice#%d has higher priority user", this._index)); } await this._kill(true); this._spawn(channel); } } else { this._spawn(channel); } } log.info("TunerDevice#%d streaming to user `%s` (priority=%d)", this._index, user.id, user.priority); user._stream = stream; this._users.add(user); if (stream.closed === true) { this.endStream(user); } else { stream.once("close", () => this.endStream(user)); } this._updated(); } endStream(user) { log.debug("TunerDevice#%d end stream for user `%s` (priority=%d)...", this._index, user.id, user.priority); user._stream.end(); this._users.delete(user); if (this._users.size === 0) { setTimeout(() => { if (this._users.size === 0 && this._process) { this._kill(true).catch(log.error); } }, 3000); } log.info("TunerDevice#%d end streaming to user `%s` (priority=%d)", this._index, user.id, user.priority); this._updated(); } async getRemotePrograms(query) { if (!this._isRemote) { throw new Error(util.format("TunerDevice#%d is not remote device", this._index)); } const client = new client_1.default(); client.host = this.config.remoteMirakurunHost; client.port = this.config.remoteMirakurunPort || 40772; client.userAgent = "Mirakurun (Remote)"; log.debug("TunerDevice#%d fetching remote programs from %s:%d...", this._index, client.host, client.port); const programs = await client.getPrograms(query); log.info("TunerDevice#%d fetched %d remote programs", this._index, programs.length); return programs; } _spawn(ch) { log.debug("TunerDevice#%d spawn...", this._index); if (this._process) { throw new Error(util.format("TunerDevice#%d has process", this._index)); } let cmd; if (this._isRemote === true) { cmd = "node lib/remote"; cmd += " " + this._config.remoteMirakurunHost; cmd += " " + (this._config.remoteMirakurunPort || 40772); cmd += " " + ch.type; cmd += " " + ch.channel; if (this._config.remoteMirakurunDecoder === true) { cmd += " decode"; } } else { cmd = this._config.command; } cmd = common.replaceCommandTemplate(cmd, { channel: ch.channel, type: ch.type, satelite: ch.commandVars?.satellite || "", space: 0, ...ch.commandVars }); const parsed = common.parseCommandForSpawn(cmd); this._process = child_process.spawn(parsed.command, parsed.args); this._command = cmd; this._channel = ch; if (this._config.dvbDevicePath) { const cat = child_process.spawn("cat", [this._config.dvbDevicePath]); cat.once("error", (err) => { log.error("TunerDevice#%d cat process error `%s` (pid=%d)", this._index, err.name, cat.pid); this._kill(false); }); cat.once("close", (code, signal) => { log.debug("TunerDevice#%d cat process has closed with code=%d by signal `%s` (pid=%d)", this._index, code, signal, cat.pid); if (this._exited === false) { this._kill(false); } }); this._process.once("exit", () => cat.kill("SIGKILL")); this._stream = cat.stdout; } else { this._stream = this._process.stdout; } this._process.once("exit", () => this._exited = true); this._process.once("error", (err) => { log.fatal("TunerDevice#%d process error `%s` (pid=%d)", this._index, err.name, this._process.pid); ++this._fatalCount; if (this._fatalCount >= 3) { log.fatal("TunerDevice#%d has something fault! **RESTART REQUIRED** after fix it.", this._index); this._isFault = true; this._closing = true; } this._end(); setTimeout(this._release.bind(this), this._config.dvbDevicePath ? 1000 : 100); }); this._process.once("close", (code, signal) => { log.info("TunerDevice#%d process has closed with exit code=%d by signal `%s` (pid=%d)", this._index, code, signal, this._process.pid); this._end(); setTimeout(this._release.bind(this), this._config.dvbDevicePath ? 1000 : 100); }); this._process.stderr.on("data", data => { log.debug("TunerDevice#%d > %s", this._index, data.toString().trim()); }); this._stream.on("data", this._streamOnData.bind(this)); this._updated(); log.info("TunerDevice#%d process has spawned by command `%s` (pid=%d)", this._index, cmd, this._process.pid); } _streamOnData(chunk) { for (const user of this._users) { user._stream.write(chunk); } } _end() { this._isAvailable = false; this._stream.removeAllListeners("data"); if (this._closing === true) { for (const user of this._users) { user._stream.end(); } this._users.clear(); } this._updated(); } async _kill(close) { log.debug("TunerDevice#%d kill...", this._index); if (!this._process || !this._process.pid) { throw new Error(util.format("TunerDevice#%d has not process", this._index)); } else if (this._closing) { log.debug("TunerDevice#%d return because it is closing", this._index); return; } this._isAvailable = false; this._closing = close; this._updated(); await new Promise(resolve => { this.once("release", resolve); if (/^dvbv5-zap /.test(this._command) === true) { this._process.kill("SIGKILL"); } else { const timer = setTimeout(() => { log.warn("TunerDevice#%d will force killed because SIGTERM timed out...", this._index); this._process.kill("SIGKILL"); }, 6000); this._process.once("exit", () => clearTimeout(timer)); this._process.kill("SIGTERM"); } }); } _release() { if (this._process) { this._process.stderr.removeAllListeners(); this._process.removeAllListeners(); } if (this._stream) { this._stream.removeAllListeners(); } this._command = null; this._process = null; this._stream = null; if (this._closing === false && this._users.size !== 0) { log.warn("TunerDevice#%d respawning because request has not closed", this._index); ++status_1.default.errorCount.tunerDeviceRespawn; this._spawn(this._channel); return; } this._fatalCount = 0; this._channel = null; this._users.clear(); if (this._isFault === false) { this._isAvailable = true; } this._closing = false; this._exited = false; this.emit("release"); log.info("TunerDevice#%d released", this._index); this._updated(); } _updated() { Event_1.default.emit("tuner", "update", this.toJSON()); } } exports.default = TunerDevice; //# sourceMappingURL=TunerDevice.js.map