@warren-bank/fx_cast_bridge 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/js/config.json +4 -0
- package/js/src/bridge/components/airplay/auth.js +251 -0
- package/js/src/bridge/components/airplay/bplist.js +13 -0
- package/js/src/bridge/components/cast/Session.js +204 -0
- package/js/src/bridge/components/cast/client.js +107 -0
- package/js/src/bridge/components/cast/discovery.js +49 -0
- package/js/src/bridge/components/cast/index.js +107 -0
- package/js/src/bridge/components/cast/remote.js +120 -0
- package/js/src/bridge/components/cast/types.js +96 -0
- package/js/src/bridge/components/mediaServer.js +231 -0
- package/js/src/bridge/index.js +161 -0
- package/js/src/bridge/lib/mdns.js +154 -0
- package/js/src/bridge/lib/ping.js +20 -0
- package/js/src/bridge/lib/subtitles.js +131 -0
- package/js/src/bridge/messaging.js +56 -0
- package/js/src/bridge/messagingTypes.js +22 -0
- package/js/src/daemon.js +181 -0
- package/js/src/main.js +236 -0
- package/js/src/transforms.js +77 -0
- package/package.json +51 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault =
|
|
3
|
+
(this && this.__importDefault) ||
|
|
4
|
+
function (mod) {
|
|
5
|
+
return mod && mod.__esModule ? mod : { default: mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.handleCastMessage = handleCastMessage;
|
|
9
|
+
const Session_1 = __importDefault(require("./Session"));
|
|
10
|
+
const client_1 = __importDefault(require("./client"));
|
|
11
|
+
const sessions = new Map();
|
|
12
|
+
function handleCastMessage(messaging, message) {
|
|
13
|
+
switch (message.subject) {
|
|
14
|
+
case "bridge:createCastSession": {
|
|
15
|
+
const { appId, receiverDevice } = message.data;
|
|
16
|
+
const session = new Session_1.default(
|
|
17
|
+
appId,
|
|
18
|
+
receiverDevice,
|
|
19
|
+
messaging,
|
|
20
|
+
sessionId => {
|
|
21
|
+
sessions.set(sessionId, session);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
case "bridge:sendCastReceiverMessage": {
|
|
27
|
+
const { sessionId, messageData, messageId } = message.data;
|
|
28
|
+
const session = sessions.get(sessionId);
|
|
29
|
+
if (!session) {
|
|
30
|
+
messaging.sendMessage({
|
|
31
|
+
subject: "cast:impl_sendMessage",
|
|
32
|
+
data: {
|
|
33
|
+
error: "Session does not exist",
|
|
34
|
+
sessionId,
|
|
35
|
+
messageId
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
session.sendReceiverMessage(messageData);
|
|
42
|
+
} catch (err) {
|
|
43
|
+
messaging.sendMessage({
|
|
44
|
+
subject: "cast:impl_sendMessage",
|
|
45
|
+
data: {
|
|
46
|
+
error: `Failed to send message (${err})`,
|
|
47
|
+
sessionId,
|
|
48
|
+
messageId
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
messaging.sendMessage({
|
|
54
|
+
subject: "cast:impl_sendMessage",
|
|
55
|
+
data: { sessionId, messageId }
|
|
56
|
+
});
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case "bridge:sendCastSessionMessage": {
|
|
60
|
+
const { namespace, sessionId, messageId } = message.data;
|
|
61
|
+
const session = sessions.get(sessionId);
|
|
62
|
+
if (!session) {
|
|
63
|
+
messaging.sendMessage({
|
|
64
|
+
subject: "cast:impl_sendMessage",
|
|
65
|
+
data: {
|
|
66
|
+
error: "Session does not exist",
|
|
67
|
+
sessionId,
|
|
68
|
+
messageId
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
let { messageData } = message.data;
|
|
75
|
+
if (typeof messageData === "string") {
|
|
76
|
+
messageData = JSON.parse(messageData);
|
|
77
|
+
}
|
|
78
|
+
session.sendMessage(namespace, messageData);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
messaging.sendMessage({
|
|
81
|
+
subject: "cast:impl_sendMessage",
|
|
82
|
+
data: {
|
|
83
|
+
error: `Failed to send message (${err})`,
|
|
84
|
+
sessionId,
|
|
85
|
+
messageId
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
messaging.sendMessage({
|
|
91
|
+
subject: "cast:impl_sendMessage",
|
|
92
|
+
data: { sessionId, messageId }
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "bridge:stopCastSession": {
|
|
97
|
+
const { receiverDevice } = message.data;
|
|
98
|
+
const client = new client_1.default();
|
|
99
|
+
client
|
|
100
|
+
.connect(receiverDevice.host, { port: receiverDevice.port })
|
|
101
|
+
.then(() => {
|
|
102
|
+
client.sendReceiverMessage({ type: "STOP" });
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault =
|
|
3
|
+
(this && this.__importDefault) ||
|
|
4
|
+
function (mod) {
|
|
5
|
+
return mod && mod.__esModule ? mod : { default: mod };
|
|
6
|
+
};
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
const client_1 = __importDefault(require("./client"));
|
|
9
|
+
const NS_MEDIA = "urn:x-cast:com.google.cast.media";
|
|
10
|
+
class Remote extends client_1.default {
|
|
11
|
+
constructor(host, options) {
|
|
12
|
+
super();
|
|
13
|
+
this.host = host;
|
|
14
|
+
this.options = options;
|
|
15
|
+
super
|
|
16
|
+
.connect(host, {
|
|
17
|
+
port:
|
|
18
|
+
options === null || options === void 0
|
|
19
|
+
? void 0
|
|
20
|
+
: options.port,
|
|
21
|
+
onReceiverMessage: message => {
|
|
22
|
+
this.onReceiverMessage(message);
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
.then(() => {
|
|
26
|
+
this.sendReceiverMessage({ type: "GET_STATUS" });
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
disconnect() {
|
|
30
|
+
var _a;
|
|
31
|
+
super.disconnect();
|
|
32
|
+
(_a = this.transportClient) === null || _a === void 0
|
|
33
|
+
? void 0
|
|
34
|
+
: _a.disconnect();
|
|
35
|
+
}
|
|
36
|
+
sendMediaMessage(message) {
|
|
37
|
+
var _a;
|
|
38
|
+
(_a = this.transportClient) === null || _a === void 0
|
|
39
|
+
? void 0
|
|
40
|
+
: _a.sendMediaMessage(message);
|
|
41
|
+
}
|
|
42
|
+
onReceiverMessage(message) {
|
|
43
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
44
|
+
if (message.type !== "RECEIVER_STATUS") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const application =
|
|
48
|
+
(_a = message.status.applications) === null || _a === void 0
|
|
49
|
+
? void 0
|
|
50
|
+
: _a[0];
|
|
51
|
+
if (!application || application.isIdleScreen) {
|
|
52
|
+
if (this.transportClient) {
|
|
53
|
+
this.transportClient = undefined;
|
|
54
|
+
(_c =
|
|
55
|
+
(_b = this.options) === null || _b === void 0
|
|
56
|
+
? void 0
|
|
57
|
+
: _b.onApplicationClose) === null || _c === void 0
|
|
58
|
+
? void 0
|
|
59
|
+
: _c.call(_b);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
(_e =
|
|
63
|
+
(_d = this.options) === null || _d === void 0
|
|
64
|
+
? void 0
|
|
65
|
+
: _d.onReceiverStatusUpdate) === null || _e === void 0
|
|
66
|
+
? void 0
|
|
67
|
+
: _e.call(_d, message.status);
|
|
68
|
+
if (application && !this.transportClient) {
|
|
69
|
+
this.transportClient = new RemoteTransport(
|
|
70
|
+
application.transportId,
|
|
71
|
+
message => this.onMediaMessage(message)
|
|
72
|
+
);
|
|
73
|
+
this.transportClient
|
|
74
|
+
.connect(this.host, {
|
|
75
|
+
port:
|
|
76
|
+
(_f = this.options) === null || _f === void 0
|
|
77
|
+
? void 0
|
|
78
|
+
: _f.port
|
|
79
|
+
})
|
|
80
|
+
.then(() => {
|
|
81
|
+
var _a;
|
|
82
|
+
(_a = this.transportClient) === null || _a === void 0
|
|
83
|
+
? void 0
|
|
84
|
+
: _a.sendMediaMessage({
|
|
85
|
+
type: "GET_STATUS",
|
|
86
|
+
requestId: 0
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
(_h =
|
|
90
|
+
(_g = this.options) === null || _g === void 0
|
|
91
|
+
? void 0
|
|
92
|
+
: _g.onApplicationFound) === null || _h === void 0
|
|
93
|
+
? void 0
|
|
94
|
+
: _h.call(_g);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
onMediaMessage(message) {
|
|
98
|
+
var _a, _b;
|
|
99
|
+
if (message.type !== "MEDIA_STATUS") {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
(_b =
|
|
103
|
+
(_a = this.options) === null || _a === void 0
|
|
104
|
+
? void 0
|
|
105
|
+
: _a.onMediaStatusUpdate) === null || _b === void 0
|
|
106
|
+
? void 0
|
|
107
|
+
: _b.call(_a, message.status[0]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
exports.default = Remote;
|
|
111
|
+
class RemoteTransport extends client_1.default {
|
|
112
|
+
constructor(transportId, onMediaMessage) {
|
|
113
|
+
super(undefined, transportId);
|
|
114
|
+
this.mediaChannel = this.createChannel(NS_MEDIA);
|
|
115
|
+
this.mediaChannel.on("message", message => onMediaMessage(message));
|
|
116
|
+
}
|
|
117
|
+
sendMediaMessage(message) {
|
|
118
|
+
this.mediaChannel.send(message);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserAction = exports.HlsVideoSegmentFormat = void 0;
|
|
4
|
+
var Capability;
|
|
5
|
+
(function (Capability) {
|
|
6
|
+
Capability["VIDEO_OUT"] = "video_out";
|
|
7
|
+
Capability["AUDIO_OUT"] = "audio_out";
|
|
8
|
+
Capability["VIDEO_IN"] = "video_in";
|
|
9
|
+
Capability["AUDIO_IN"] = "audio_in";
|
|
10
|
+
Capability["MULTIZONE_GROUP"] = "multizone_group";
|
|
11
|
+
})(Capability || (Capability = {}));
|
|
12
|
+
var ReceiverType;
|
|
13
|
+
(function (ReceiverType) {
|
|
14
|
+
ReceiverType["CAST"] = "cast";
|
|
15
|
+
ReceiverType["DIAL"] = "dial";
|
|
16
|
+
ReceiverType["HANGOUT"] = "hangout";
|
|
17
|
+
ReceiverType["CUSTOM"] = "custom";
|
|
18
|
+
})(ReceiverType || (ReceiverType = {}));
|
|
19
|
+
var VolumeControlType;
|
|
20
|
+
(function (VolumeControlType) {
|
|
21
|
+
VolumeControlType["ATTENUATION"] = "attenuation";
|
|
22
|
+
VolumeControlType["FIXED"] = "fixed";
|
|
23
|
+
VolumeControlType["MASTER"] = "master";
|
|
24
|
+
})(VolumeControlType || (VolumeControlType = {}));
|
|
25
|
+
var IdleReason;
|
|
26
|
+
(function (IdleReason) {
|
|
27
|
+
IdleReason["CANCELLED"] = "CANCELLED";
|
|
28
|
+
IdleReason["INTERRUPTED"] = "INTERRUPTED";
|
|
29
|
+
IdleReason["FINISHED"] = "FINISHED";
|
|
30
|
+
IdleReason["ERROR"] = "ERROR";
|
|
31
|
+
})(IdleReason || (IdleReason = {}));
|
|
32
|
+
var HlsSegmentFormat;
|
|
33
|
+
(function (HlsSegmentFormat) {
|
|
34
|
+
HlsSegmentFormat["AAC"] = "aac";
|
|
35
|
+
HlsSegmentFormat["AC3"] = "ac3";
|
|
36
|
+
HlsSegmentFormat["MP3"] = "mp3";
|
|
37
|
+
HlsSegmentFormat["TS"] = "ts";
|
|
38
|
+
HlsSegmentFormat["TS_AAC"] = "ts_aac";
|
|
39
|
+
HlsSegmentFormat["E_AC3"] = "e_ac3";
|
|
40
|
+
HlsSegmentFormat["FMP4"] = "fmp4";
|
|
41
|
+
})(HlsSegmentFormat || (HlsSegmentFormat = {}));
|
|
42
|
+
var HlsVideoSegmentFormat;
|
|
43
|
+
(function (HlsVideoSegmentFormat) {
|
|
44
|
+
HlsVideoSegmentFormat["MPEG2_TS"] = "mpeg2_ts";
|
|
45
|
+
HlsVideoSegmentFormat["FMP4"] = "fmp4";
|
|
46
|
+
})(
|
|
47
|
+
HlsVideoSegmentFormat ||
|
|
48
|
+
(exports.HlsVideoSegmentFormat = HlsVideoSegmentFormat = {})
|
|
49
|
+
);
|
|
50
|
+
var MetadataType;
|
|
51
|
+
(function (MetadataType) {
|
|
52
|
+
MetadataType[(MetadataType["GENERIC"] = 0)] = "GENERIC";
|
|
53
|
+
MetadataType[(MetadataType["MOVIE"] = 1)] = "MOVIE";
|
|
54
|
+
MetadataType[(MetadataType["TV_SHOW"] = 2)] = "TV_SHOW";
|
|
55
|
+
MetadataType[(MetadataType["MUSIC_TRACK"] = 3)] = "MUSIC_TRACK";
|
|
56
|
+
MetadataType[(MetadataType["PHOTO"] = 4)] = "PHOTO";
|
|
57
|
+
MetadataType[(MetadataType["AUDIOBOOK_CHAPTER"] = 5)] = "AUDIOBOOK_CHAPTER";
|
|
58
|
+
})(MetadataType || (MetadataType = {}));
|
|
59
|
+
var PlayerState;
|
|
60
|
+
(function (PlayerState) {
|
|
61
|
+
PlayerState["IDLE"] = "IDLE";
|
|
62
|
+
PlayerState["PLAYING"] = "PLAYING";
|
|
63
|
+
PlayerState["PAUSED"] = "PAUSED";
|
|
64
|
+
PlayerState["BUFFERING"] = "BUFFERING";
|
|
65
|
+
})(PlayerState || (PlayerState = {}));
|
|
66
|
+
var RepeatMode;
|
|
67
|
+
(function (RepeatMode) {
|
|
68
|
+
RepeatMode["OFF"] = "REPEAT_OFF";
|
|
69
|
+
RepeatMode["ALL"] = "REPEAT_ALL";
|
|
70
|
+
RepeatMode["SINGLE"] = "REPEAT_SINGLE";
|
|
71
|
+
RepeatMode["ALL_AND_SHUFFLE"] = "REPEAT_ALL_AND_SHUFFLE";
|
|
72
|
+
})(RepeatMode || (RepeatMode = {}));
|
|
73
|
+
var ResumeState;
|
|
74
|
+
(function (ResumeState) {
|
|
75
|
+
ResumeState["PLAYBACK_START"] = "PLAYBACK_START";
|
|
76
|
+
ResumeState["PLAYBACK_PAUSE"] = "PLAYBACK_PAUSE";
|
|
77
|
+
})(ResumeState || (ResumeState = {}));
|
|
78
|
+
var StreamType;
|
|
79
|
+
(function (StreamType) {
|
|
80
|
+
StreamType["BUFFERED"] = "BUFFERED";
|
|
81
|
+
StreamType["LIVE"] = "LIVE";
|
|
82
|
+
StreamType["OTHER"] = "OTHER";
|
|
83
|
+
})(StreamType || (StreamType = {}));
|
|
84
|
+
var TrackType;
|
|
85
|
+
(function (TrackType) {
|
|
86
|
+
TrackType["TEXT"] = "TEXT";
|
|
87
|
+
TrackType["AUDIO"] = "AUDIO";
|
|
88
|
+
TrackType["VIDEO"] = "VIDEO";
|
|
89
|
+
})(TrackType || (TrackType = {}));
|
|
90
|
+
var UserAction;
|
|
91
|
+
(function (UserAction) {
|
|
92
|
+
UserAction["LIKE"] = "LIKE";
|
|
93
|
+
UserAction["DISLIKE"] = "DISLIKE";
|
|
94
|
+
UserAction["FOLLOW"] = "FOLLOW";
|
|
95
|
+
UserAction["UNFOLLOW"] = "UNFOLLOW";
|
|
96
|
+
})(UserAction || (exports.UserAction = UserAction = {}));
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter =
|
|
3
|
+
(this && this.__awaiter) ||
|
|
4
|
+
function (thisArg, _arguments, P, generator) {
|
|
5
|
+
function adopt(value) {
|
|
6
|
+
return value instanceof P
|
|
7
|
+
? value
|
|
8
|
+
: new P(function (resolve) {
|
|
9
|
+
resolve(value);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
13
|
+
function fulfilled(value) {
|
|
14
|
+
try {
|
|
15
|
+
step(generator.next(value));
|
|
16
|
+
} catch (e) {
|
|
17
|
+
reject(e);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function rejected(value) {
|
|
21
|
+
try {
|
|
22
|
+
step(generator["throw"](value));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
reject(e);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function step(result) {
|
|
28
|
+
result.done
|
|
29
|
+
? resolve(result.value)
|
|
30
|
+
: adopt(result.value).then(fulfilled, rejected);
|
|
31
|
+
}
|
|
32
|
+
step(
|
|
33
|
+
(generator = generator.apply(thisArg, _arguments || [])).next()
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
var __importDefault =
|
|
38
|
+
(this && this.__importDefault) ||
|
|
39
|
+
function (mod) {
|
|
40
|
+
return mod && mod.__esModule ? mod : { default: mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.mediaServer = void 0;
|
|
44
|
+
exports.startMediaServer = startMediaServer;
|
|
45
|
+
exports.stopMediaServer = stopMediaServer;
|
|
46
|
+
const fs_1 = __importDefault(require("fs"));
|
|
47
|
+
const http_1 = __importDefault(require("http"));
|
|
48
|
+
const os_1 = __importDefault(require("os"));
|
|
49
|
+
const path_1 = __importDefault(require("path"));
|
|
50
|
+
const stream_1 = __importDefault(require("stream"));
|
|
51
|
+
const mime_types_1 = __importDefault(require("mime-types"));
|
|
52
|
+
const subtitles_1 = require("../lib/subtitles");
|
|
53
|
+
function startMediaServer(messaging, filePath, port) {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
if (
|
|
56
|
+
exports.mediaServer === null || exports.mediaServer === void 0
|
|
57
|
+
? void 0
|
|
58
|
+
: exports.mediaServer.listening
|
|
59
|
+
) {
|
|
60
|
+
yield stopMediaServer();
|
|
61
|
+
}
|
|
62
|
+
let fileDir;
|
|
63
|
+
let fileName;
|
|
64
|
+
let fileSize;
|
|
65
|
+
try {
|
|
66
|
+
const stat = yield fs_1.default.promises.lstat(filePath);
|
|
67
|
+
if (stat.isFile()) {
|
|
68
|
+
fileDir = path_1.default.dirname(filePath);
|
|
69
|
+
fileName = path_1.default.basename(filePath);
|
|
70
|
+
fileSize = stat.size;
|
|
71
|
+
} else {
|
|
72
|
+
messaging.sendMessage({
|
|
73
|
+
subject: "mediaCast:mediaServerError",
|
|
74
|
+
data: "Media path is not a file."
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
messaging.sendMessage({
|
|
80
|
+
subject: "mediaCast:mediaServerError",
|
|
81
|
+
data: "Failed to find media path."
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const contentType = mime_types_1.default.lookup(filePath);
|
|
86
|
+
if (!contentType) {
|
|
87
|
+
messaging.sendMessage({
|
|
88
|
+
subject: "mediaCast:mediaServerError",
|
|
89
|
+
data: "Failed to find media type."
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const subtitles = new Map();
|
|
94
|
+
try {
|
|
95
|
+
const dirEntries = yield fs_1.default.promises.readdir(fileDir, {
|
|
96
|
+
withFileTypes: true
|
|
97
|
+
});
|
|
98
|
+
for (const dirEntry of dirEntries) {
|
|
99
|
+
if (
|
|
100
|
+
dirEntry.isFile() &&
|
|
101
|
+
mime_types_1.default.lookup(dirEntry.name) ===
|
|
102
|
+
"application/x-subrip"
|
|
103
|
+
) {
|
|
104
|
+
subtitles.set(
|
|
105
|
+
dirEntry.name,
|
|
106
|
+
yield (0, subtitles_1.convertSrtToVtt)(
|
|
107
|
+
path_1.default.join(fileDir, dirEntry.name)
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error(
|
|
114
|
+
`Error: Failed to find/convert subtitles (${filePath}).`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
exports.mediaServer = http_1.default.createServer((req, res) =>
|
|
118
|
+
__awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
if (!req.url) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
let decodedUrl = decodeURIComponent(req.url);
|
|
123
|
+
if (decodedUrl.startsWith("/")) {
|
|
124
|
+
decodedUrl = decodedUrl.slice(1);
|
|
125
|
+
}
|
|
126
|
+
switch (decodedUrl) {
|
|
127
|
+
case fileName: {
|
|
128
|
+
const { range } = req.headers;
|
|
129
|
+
if (range) {
|
|
130
|
+
const bounds = range.substring(6).split("-");
|
|
131
|
+
const start = parseInt(bounds[0]);
|
|
132
|
+
const end = bounds[1]
|
|
133
|
+
? parseInt(bounds[1])
|
|
134
|
+
: fileSize - 1;
|
|
135
|
+
res.writeHead(206, {
|
|
136
|
+
"Accept-Ranges": "bytes",
|
|
137
|
+
"Content-Range": `bytes ${start}-${end}/${fileSize}`,
|
|
138
|
+
"Content-Length": end - start + 1,
|
|
139
|
+
"Content-Type": contentType
|
|
140
|
+
});
|
|
141
|
+
fs_1.default
|
|
142
|
+
.createReadStream(filePath, { start, end })
|
|
143
|
+
.pipe(res);
|
|
144
|
+
} else {
|
|
145
|
+
res.writeHead(200, {
|
|
146
|
+
"Content-Length": fileSize,
|
|
147
|
+
"Content-Type": contentType
|
|
148
|
+
});
|
|
149
|
+
fs_1.default.createReadStream(filePath).pipe(res);
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
default: {
|
|
154
|
+
if (subtitles.has(req.url)) {
|
|
155
|
+
const vttSource = subtitles.get(req.url);
|
|
156
|
+
const vttStream =
|
|
157
|
+
stream_1.default.Readable.from(vttSource);
|
|
158
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
159
|
+
vttStream.pipe(res);
|
|
160
|
+
}
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
);
|
|
166
|
+
exports.mediaServer.on("close", () => {
|
|
167
|
+
messaging.sendMessage({
|
|
168
|
+
subject: "mediaCast:mediaServerStopped"
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
exports.mediaServer.on("error", err => {
|
|
172
|
+
messaging.sendMessage({
|
|
173
|
+
subject: "mediaCast:mediaServerError",
|
|
174
|
+
data: err.message
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
exports.mediaServer.listen(port, () => {
|
|
178
|
+
const localAddresses = [];
|
|
179
|
+
for (const iface of Object.values(
|
|
180
|
+
os_1.default.networkInterfaces()
|
|
181
|
+
)) {
|
|
182
|
+
const matchingIface =
|
|
183
|
+
iface === null || iface === void 0
|
|
184
|
+
? void 0
|
|
185
|
+
: iface.find(
|
|
186
|
+
details =>
|
|
187
|
+
details.family === "IPv4" && !details.internal
|
|
188
|
+
);
|
|
189
|
+
if (matchingIface) {
|
|
190
|
+
localAddresses.push(matchingIface.address);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!localAddresses.length) {
|
|
194
|
+
messaging.sendMessage({
|
|
195
|
+
subject: "mediaCast:mediaServerError",
|
|
196
|
+
data: "Failed to get local address."
|
|
197
|
+
});
|
|
198
|
+
stopMediaServer();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
messaging.sendMessage({
|
|
202
|
+
subject: "mediaCast:mediaServerStarted",
|
|
203
|
+
data: {
|
|
204
|
+
mediaPath: fileName,
|
|
205
|
+
subtitlePaths: Array.from(subtitles.keys()),
|
|
206
|
+
localAddress: localAddresses[0]
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function stopMediaServer() {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
if (
|
|
215
|
+
!(exports.mediaServer === null || exports.mediaServer === void 0
|
|
216
|
+
? void 0
|
|
217
|
+
: exports.mediaServer.listening)
|
|
218
|
+
) {
|
|
219
|
+
resolve();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
exports.mediaServer.close(err => {
|
|
223
|
+
if (err) {
|
|
224
|
+
reject();
|
|
225
|
+
} else {
|
|
226
|
+
resolve();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
exports.mediaServer = undefined;
|
|
230
|
+
});
|
|
231
|
+
}
|