-
-
Notifications
You must be signed in to change notification settings - Fork 316
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
f61b852
commit 3305c19
Showing
6 changed files
with
173 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,86 +1,129 @@ | ||
import TrackerClient, { TrackerEventHandler } from "bittorrent-tracker"; | ||
import TrackerClient, { PeerCandidate } from "bittorrent-tracker"; | ||
import * as RIPEMD160 from "ripemd160"; | ||
import { Peer } from "./peer"; | ||
import * as PeerUtil from "./peer-utils"; | ||
import { Segment } from "./types"; | ||
import { Segment, StreamWithSegments } from "./types"; | ||
import { JsonSegmentAnnouncementMap } from "./internal-types"; | ||
import { SegmentsMemoryStorage } from "./segments-storage"; | ||
import * as Utils from "./utils"; | ||
|
||
export class P2PLoader { | ||
private readonly streamExternalId: string; | ||
private readonly streamHash: string; | ||
private readonly peerHash: string; | ||
private trackerClient: TrackerClient; | ||
private readonly trackerClient: TrackerClient; | ||
private readonly peers = new Map<string, Peer>(); | ||
private announcementMap: JsonSegmentAnnouncementMap = {}; | ||
|
||
constructor(streamExternalId: string) { | ||
this.streamExternalId = streamExternalId; | ||
constructor( | ||
private streamManifestUrl: string, | ||
private readonly stream: StreamWithSegments, | ||
private readonly segmentStorage: SegmentsMemoryStorage | ||
) { | ||
const peerId = PeerUtil.generatePeerId(); | ||
this.streamExternalId = Utils.getStreamExternalId( | ||
this.stream, | ||
this.streamManifestUrl | ||
); | ||
this.streamHash = getHash(this.streamExternalId); | ||
this.peerHash = getHash(peerId); | ||
|
||
this.trackerClient = new TrackerClient({ | ||
infoHash: this.streamHash, | ||
peerId: this.peerHash, | ||
port: 6881, | ||
announce: [ | ||
"wss://tracker.novage.com.ua", | ||
"wss://tracker.openwebtorrent.com", | ||
], | ||
rtcConfig: { | ||
iceServers: [ | ||
{ | ||
urls: [ | ||
"stun:stun.l.google.com:19302", | ||
"stun:global.stun.twilio.com:3478", | ||
], | ||
}, | ||
], | ||
}, | ||
this.trackerClient = createTrackerClient({ | ||
streamHash: this.streamHash, | ||
peerHash: this.peerHash, | ||
}); | ||
this.subscribeOnTrackerEvents(this.trackerClient); | ||
this.segmentStorage.subscribeOnUpdate( | ||
this.onSegmentStorageUpdate.bind(this) | ||
); | ||
this.trackerClient.start(); | ||
} | ||
|
||
this.trackerClient.on("update", this.onTrackerUpdate); | ||
this.trackerClient.on("peer", this.onTrackerPeerConnect); | ||
this.trackerClient.on("warning", this.onTrackerWarning); | ||
this.trackerClient.on("error", this.onTrackerError); | ||
private subscribeOnTrackerEvents(trackerClient: TrackerClient) { | ||
// TODO: tracker event handlers | ||
trackerClient.on("update", () => {}); | ||
trackerClient.on("peer", (candidate) => { | ||
const peer = this.peers.get(candidate.id); | ||
if (peer) peer.addCandidate(candidate); | ||
else this.createPeer(candidate); | ||
}); | ||
trackerClient.on("warning", (warning) => {}); | ||
trackerClient.on("error", (error) => {}); | ||
} | ||
|
||
this.trackerClient.start(); | ||
private createPeer(candidate: PeerCandidate) { | ||
const peer = new Peer(candidate, { | ||
onPeerConnected: this.onPeerConnected.bind(this), | ||
onSegmentRequested: this.onSegmentRequested.bind(this), | ||
}); | ||
this.peers.set(candidate.id, peer); | ||
} | ||
|
||
private onTrackerUpdate: TrackerEventHandler<"update"> = (data) => {}; | ||
private onTrackerPeerConnect: TrackerEventHandler<"peer"> = (candidate) => { | ||
const peer = this.peers.get(candidate.id); | ||
if (peer) { | ||
peer.addCandidate(candidate); | ||
} else { | ||
const peer = new Peer(this.streamExternalId, candidate); | ||
this.peers.set(candidate.id, peer); | ||
private async onSegmentStorageUpdate() { | ||
const storedSegmentIds = await this.segmentStorage.getStoredSegmentIds(); | ||
const loaded: Segment[] = []; | ||
|
||
for (const id of storedSegmentIds) { | ||
const segment = this.stream.segments.get(id); | ||
if (!segment) continue; | ||
|
||
loaded.push(segment); | ||
} | ||
}; | ||
private onTrackerWarning: TrackerEventHandler<"warning"> = (warning) => {}; | ||
private onTrackerError: TrackerEventHandler<"error"> = (error) => {}; | ||
|
||
updateSegmentsLoadingState(loaded: Segment[], loading: Segment[]) { | ||
this.announcementMap = PeerUtil.getJsonSegmentsAnnouncementMap( | ||
this.streamExternalId, | ||
loaded, | ||
loading | ||
[] | ||
); | ||
this.broadcastSegmentAnnouncement(); | ||
} | ||
|
||
sendSegmentsAnnouncementToPeer(peer: Peer) { | ||
if (!peer?.isConnected) return; | ||
private onPeerConnected(peer: Peer) { | ||
peer.sendSegmentsAnnouncement(this.announcementMap); | ||
} | ||
|
||
broadcastSegmentAnnouncement() { | ||
private async onSegmentRequested(peer: Peer, segmentExternalId: string) { | ||
const segmentData = await this.segmentStorage.getSegment(segmentExternalId); | ||
if (segmentData) peer.sendSegmentData(segmentExternalId, segmentData); | ||
else peer.sendSegmentAbsent(segmentExternalId); | ||
} | ||
|
||
private broadcastSegmentAnnouncement() { | ||
for (const peer of this.peers.values()) { | ||
this.sendSegmentsAnnouncementToPeer(peer); | ||
if (!peer.isConnected) continue; | ||
peer.sendSegmentsAnnouncement(this.announcementMap); | ||
} | ||
} | ||
} | ||
|
||
function getHash(data: string) { | ||
return new RIPEMD160().update(data).digest("hex"); | ||
} | ||
|
||
function createTrackerClient({ | ||
streamHash, | ||
peerHash, | ||
}: { | ||
streamHash: string; | ||
peerHash: string; | ||
}) { | ||
return new TrackerClient({ | ||
infoHash: streamHash, | ||
peerId: peerHash, | ||
port: 6881, | ||
announce: [ | ||
"wss://tracker.novage.com.ua", | ||
"wss://tracker.openwebtorrent.com", | ||
], | ||
rtcConfig: { | ||
iceServers: [ | ||
{ | ||
urls: [ | ||
"stun:stun.l.google.com:19302", | ||
"stun:global.stun.twilio.com:3478", | ||
], | ||
}, | ||
], | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.