Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
// W069 is “['x'] is better written in dot notation”, but Closure Compiler wants ['x'].
// jshint -W069
// jshint bitwise: false
// ==ClosureCompiler==
// @output_file_name tty-player.min.js
// @compilation_level ADVANCED_OPTIMIZATIONS
// ==/ClosureCompiler==
/* global MediaError, TimeRanges, Terminal, HTMLElement */
;(function() {
"use strict";
var textDecoder = "TextDecoder" in window ? new TextDecoder() : null;
/// @param {Array<number>|Uint8Array} array
function decodeUTF8(array) {
if (array instanceof Array || !textDecoder) {
return decodeURIComponent(Array.prototype.map.call(array, function(ord) {
return "%" + ("0" + ord.toString(16)).substr(-2);
}).join(""));
} else {
return textDecoder.decode(array);
}
}
/// parseDataURI("data:foo/bar;base64,MTIzNA==#foo") === "1234"
/// @param {string} uri
function parseDataURI(uri) {
// [whole uri, "base64" or undefined, data]
var chunks = /^data:([^,]*),([^#]+)/.exec(uri);
if (chunks === null) {
return null;
}
var data = decodeURIComponent(chunks[2]);
var mime = chunks[1].replace(/;base64$/, "");
return [mime, mime === chunks[1] ? data : atob(data)];
}
/// @param {Array<number>|Uint8Array} array
function byteArrayToString(array) {
// String.fromCharCode.apply can for too large values overflow the call stack.
// Hence this, though I doubt we actually use large enough strings to worry.
// http://stackoverflow.com/a/12713326
var CHUNK_SIZE = 0x8000;
var c = [];
for (var i = 0; i < array.length; i += CHUNK_SIZE) {
c.push(String.fromCharCode.apply(null, array["subarray" in array ? "subarray" : "slice"](i, i + CHUNK_SIZE)));
}
return c.join("");
}
function parseNPT(npt) {
// Format: [npt:]([h:]mm:ss|seconds)[.subsecond]
// I’ve decided to be lazy and allow "1:2:3.4" as well as "1:02:03.4"
// This makes it [npt:][[h:]m:]s[.subsecond]
var match = /^(?:npt:)?(?:(?:(\d+):)?(\d+):)?(\d+(?:\.\d+)?)$/i.exec(npt);
return match ? (match[1] || 0) * 3600 + (match[2] || 0) * 60 + match[3] : null;
}
function classifyPosterURL(url) {
if (!url) {
// There is no poster.
return {type: null};
}
switch (/^(?:(.*):)?/.exec(url)[1]) {
case "npt":
var time = parseNPT(url);
return time ? {type: "npt", time: time} : {type: null};
case "data":
var data = parseDataURI(url);
if (/^text\/plain$/i.test(data[0])) {
return {type: "text", data: data[1]};
}
}
// TODO: treat all the other possibilities as images.
return {type: null};
}
/// @param {ArrayBuffer|Array<number>} source
function parseTTYRec(source) {
var isArray = source instanceof Array;
var utf8 = true;
var dimensions = null;
var data = [];
var byteOffset = 0;
var timeOffset = 0;
var sourceLength = isArray ? source.length : source.byteLength;
while (byteOffset < sourceLength) {
var sec, usec, len;
if (!isArray) {
var header = new DataView(source, byteOffset);
sec = header.getUint32(0, true);
usec = header.getUint32(4, true);
len = header.getUint32(8, true);
} else {
sec = source[byteOffset] +
(source[byteOffset + 1] << 8) +
(source[byteOffset + 2] << 16) +
(source[byteOffset + 3] << 24);
usec = source[byteOffset + 4] +
(source[byteOffset + 5] << 8) +
(source[byteOffset + 6] << 16) +
(source[byteOffset + 7] << 24);
len = source[byteOffset + 8] +
(source[byteOffset + 9] << 8) +
(source[byteOffset + 10] << 16) +
(source[byteOffset + 11] << 24);
}
var time = sec + (usec / 1000000);
byteOffset += 12;
var payload = isArray ? source.slice(byteOffset, byteOffset + len)
: new Uint8Array(source, byteOffset, len);
payload = utf8 ? decodeUTF8(payload) : byteArrayToString(payload);
if (byteOffset === 12) {
// First chunk might be metadata; this is how termrec does it, for example.
timeOffset = time;
var metadata = /^\x1b%(G|@)\x1b\[8;([0-9]+);([0-9]+)t$/.exec(payload);
if (metadata) {
utf8 = metadata[1] === "G";
dimensions = {
rows: +metadata[2],
cols: +metadata[3]
};
}
}
time -= timeOffset;
byteOffset += len;
data.push([payload, time]);
}
return {
// Heuristic: if the time offset is large enough, it’s probably a timestamp.
startDate: timeOffset >= 1e8 ? new Date(timeOffset * 1000) : null,
dimensions: dimensions,
data: data
};
}
function formatTime(time) {
var seconds = time | 0;
var minutes = seconds / 60 | 0;
seconds = ("0" + (seconds % 60)).substr(-2);
if (minutes >= 60) {
var hours = minutes / 60 | 0;
minutes = ("0" + (minutes % 60)).substr(-2);
return hours + ":" + minutes + ":" + seconds;
} else {
return minutes + ":" + seconds;
}
}
function blankableAttributeProperty(name) {
return {
get: function() {
var value = this.getAttribute(name);
return value === null ? "" : value.trim();
},
set: function(value) {
this.setAttribute(name, value);
}
};
}
function attributeBooleanProperty(name) {
return {
get: function() {
return this.hasAttribute(name);
},
set: function(bool) {
if (bool) {
this.setAttribute(name, "");
} else {
this.removeAttribute(name);
}
}
};
}
function invalidStateError() {
document.createElement("video").currentTime = 1;
}
/** @const */ var NETWORK_EMPTY = 0;
/** @const */ var NETWORK_IDLE = 1;
/** @const */ var NETWORK_LOADING = 2;
/** @const */ var NETWORK_NO_SOURCE = 3;
/** @const */ var HAVE_NOTHING = 0;
/** @const */ var HAVE_METADATA = 1;
/** @const */ var HAVE_CURRENT_DATA = 2;
/** @const */ var HAVE_FUTURE_DATA = 3;
/** @const */ var HAVE_ENOUGH_DATA = 4;
// Annoyingly, with things like MediaError, one apparently can’t construct them in any way.
// I might need to do something like this.
var My = {};
// Note that the constants on MediaError are *not* on My.MediaError, though they are on instances.
My.MediaError = /** @constructor */ function(code) {
Object.defineProperty(this, "code", {value: code});
};
My.MediaError.prototype = Object.create(MediaError.prototype);
/** @const */ var EMPTY_TIME_RANGES = document.createElement("video").played;
Loading
Loading full blame...