neo-neo-bblessed
Version:
A fork of neo-blessed (which is a fork of blessed) with bug fixes and maintenance.
223 lines (185 loc) • 5 kB
text/typescript
/**
* video.js - video element for blessed
* Copyright (c) 2013-2015, Christopher Jeffrey and contributors (MIT License).
* https://github.com/chjj/blessed
*/
/**
* Modules
*/
import * as cp from 'child_process';
import Node from './node.js';
import boxFactory from './box.js';
const Box = boxFactory.Box;
import Terminal from './terminal.js';
/**
* Type definitions
*/
import { VideoOptions, VideoInterface } from '../types/index';
/**
* Interfaces
*/
interface TerminalOptions {
parent: any;
left: number;
top: number;
width: number;
height: number;
shell: string;
args: string[];
start?: number;
}
interface VideoTerminal {
pty: {
write(data: string): void;
};
destroy(): void;
}
interface VideoScreen {
render(): void;
}
/**
* Video - Modern ES6 Class
*/
class Video extends Box {
type = 'video';
now: number = 0;
start: number = 0;
tty?: VideoTerminal;
constructor(options?: VideoOptions) {
// Handle malformed options gracefully
if (!options || typeof options !== 'object' || Array.isArray(options)) {
options = {};
}
super(options);
this._initializeVideo(options);
}
private _initializeVideo(options: VideoOptions): void {
let shell: string;
let args: string[];
if (this.exists('mplayer')) {
shell = 'mplayer';
args = ['-vo', 'caca', '-quiet', options.file || ''];
} else if (this.exists('mpv')) {
shell = 'mpv';
args = ['--vo', 'caca', '--really-quiet', options.file || ''];
} else {
this.parseTags = true;
this.setContent(
'{red-fg}{bold}Error:{/bold}' +
' mplayer or mpv not installed.{/red-fg}'
);
return;
}
// Calculate dimensions and ensure they're valid
let termWidth = this.width - this.iwidth;
let termHeight = this.height - this.iheight;
// Ensure minimum valid dimensions
if (isNaN(termWidth) || termWidth <= 0) termWidth = 80;
if (isNaN(termHeight) || termHeight <= 0) termHeight = 24;
const opts: TerminalOptions = {
parent: this,
left: 0,
top: 0,
width: termWidth,
height: termHeight,
shell: shell,
args: args.slice(),
};
this.now = (Date.now() / 1000) | 0;
this.start = options.start || 0;
if (this.start) {
if (shell === 'mplayer') {
opts.args.unshift('-ss', this.start + '');
} else if (shell === 'mpv') {
opts.args.unshift('--start', this.start + '');
}
}
const DISPLAY = process.env.DISPLAY;
delete process.env.DISPLAY;
this.tty = new Terminal(opts);
process.env.DISPLAY = DISPLAY;
this._setupEventHandlers(shell, args);
}
private _setupEventHandlers(shell: string, args: string[]): void {
this.on('click', () => {
if (this.tty && this.tty.pty) {
this.tty.pty.write('p');
}
});
// mplayer/mpv cannot resize itself in the terminal, so we have
// to restart it at the correct start time.
this.on('resize', () => {
if (this.tty) {
this.tty.destroy();
}
// Calculate dimensions and ensure they're valid
let termWidth = this.width - this.iwidth;
let termHeight = this.height - this.iheight;
// Ensure minimum valid dimensions
if (isNaN(termWidth) || termWidth <= 0) termWidth = 80;
if (isNaN(termHeight) || termHeight <= 0) termHeight = 24;
const opts: TerminalOptions = {
parent: this,
left: 0,
top: 0,
width: termWidth,
height: termHeight,
shell: shell,
args: args.slice(),
};
const watched = ((Date.now() / 1000) | 0) - this.now;
this.now = (Date.now() / 1000) | 0;
this.start += watched;
if (shell === 'mplayer') {
opts.args.unshift('-ss', this.start + '');
} else if (shell === 'mpv') {
opts.args.unshift('--start', this.start + '');
}
const DISPLAY = process.env.DISPLAY;
delete process.env.DISPLAY;
this.tty = new Terminal(opts);
process.env.DISPLAY = DISPLAY;
(this.screen as VideoScreen).render();
});
}
exists(program: string): boolean {
try {
return !!+cp
.execSync(
'type ' + program + ' > /dev/null 2> /dev/null' + ' && echo 1',
{ encoding: 'utf8' }
)
.trim();
} catch (e) {
return false;
}
}
// Video control methods for interface compatibility
play(): void {
if (this.tty && this.tty.pty) {
this.tty.pty.write('p');
}
}
pause(): void {
if (this.tty && this.tty.pty) {
this.tty.pty.write('p');
}
}
stop(): void {
if (this.tty) {
this.tty.destroy();
}
}
}
/**
* Factory function for backward compatibility
*/
function video(options?: VideoOptions): VideoInterface {
return new Video(options) as VideoInterface;
}
// Attach the class as a property for direct access
video.Video = Video;
/**
* Expose
*/
export default video;