11<!DOCTYPE html>
22< html lang ="en ">
33< head >
4- < meta name ="viewport " content ="width=device-width, initial-scale=1 ">
5- < title > go2rtc - WebTorrent</ title >
4+ < meta charset ="UTF-8 ">
5+ < title > go2rtc</ title >
6+ < meta http-equiv ="refresh " content ="2; URL='https://github.com/AlexxIT/go2rtc' "/>
67 < style >
7- body {
8- background-color : black;
9- margin : 0 ;
10- padding : 0 ;
11- }
12-
13- html , body , video {
8+ body , html {
149 height : 100% ;
15- width : 100% ;
16- }
17-
18- div {
19- position : absolute;
20- top : 50% ;
21- left : 50% ;
10+ margin : 0 ;
2211 display : flex;
2312 flex-direction : column;
24- transform : translateX (-50% ) translateY (-50% );
13+ justify-content : center;
14+ align-items : center;
15+ background-color : white;
16+ }
17+
18+ img {
19+ max-width : 100% ;
20+ height : auto;
2521 }
2622 </ style >
2723</ head >
2824< body >
29- < video id ="video " autoplay controls playsinline muted > </ video >
30- < div id ="login ">
31- < input id ="share " type ="text " placeholder ="share ">
32- < input id ="pwd " type ="text " placeholder ="password ">
33- < button id ="connect "> connect</ button >
34- </ div >
35- < script >
36- async function PeerConnection ( media ) {
37- const pc = new RTCPeerConnection ( {
38- iceServers : [ { urls : 'stun:stun.l.google.com:19302' } ]
39- } )
40-
41- const localTracks = [ ]
42-
43- if ( / c a m e r a | m i c r o p h o n e / . test ( media ) ) {
44- const tracks = await getMediaTracks ( 'user' , {
45- video : media . indexOf ( 'camera' ) >= 0 ,
46- audio : media . indexOf ( 'microphone' ) >= 0 ,
47- } )
48- tracks . forEach ( track => {
49- pc . addTransceiver ( track , { direction : 'sendonly' } )
50- if ( track . kind === 'video' ) localTracks . push ( track )
51- } )
52- }
53-
54- if ( media . indexOf ( 'display' ) >= 0 ) {
55- const tracks = await getMediaTracks ( 'display' , {
56- video : true ,
57- audio : media . indexOf ( 'speaker' ) >= 0 ,
58- } )
59- tracks . forEach ( track => {
60- pc . addTransceiver ( track , { direction : 'sendonly' } )
61- if ( track . kind === 'video' ) localTracks . push ( track )
62- } )
63- }
64-
65- if ( / v i d e o | a u d i o / . test ( media ) ) {
66- const tracks = [ 'video' , 'audio' ]
67- . filter ( kind => media . indexOf ( kind ) >= 0 )
68- . map ( kind => pc . addTransceiver ( kind , { direction : 'recvonly' } ) . receiver . track )
69- localTracks . push ( ...tracks )
70- }
71-
72- document . getElementById ( 'video' ) . srcObject = new MediaStream ( localTracks )
73-
74- return pc
75- }
76-
77- async function getMediaTracks ( media , constraints ) {
78- try {
79- const stream = media === 'user'
80- ? await navigator . mediaDevices . getUserMedia ( constraints )
81- : await navigator . mediaDevices . getDisplayMedia ( constraints )
82- return stream . getTracks ( )
83- } catch ( e ) {
84- console . warn ( e )
85- return [ ]
86- }
87- }
88-
89- function getOffer ( pc , timeout ) {
90- return new Promise ( ( resolve , reject ) => {
91- pc . addEventListener ( 'icegatheringstatechange' , ( ) => {
92- if ( pc . iceGatheringState === 'complete' ) resolve ( pc . localDescription . sdp )
93- } )
94-
95- pc . createOffer ( ) . then ( offer => pc . setLocalDescription ( offer ) )
96-
97- setTimeout ( ( ) => resolve ( pc . localDescription . sdp ) , timeout || 5000 )
98- } )
99- }
100- </ script >
101- < script >
102- function decode ( buffer ) {
103- return String . fromCharCode ( ...new Uint8Array ( buffer ) )
104- }
105-
106- function encode ( string ) {
107- return Uint8Array . from ( string , c => c . charCodeAt ( 0 ) )
108- }
109-
110- async function cipher ( share , pwd ) {
111- const hash = await crypto . subtle . digest ( 'SHA-256' , encode ( share ) )
112- const nonce = ( Date . now ( ) * 1000000 ) . toString ( 36 )
113-
114- const ivData = await crypto . subtle . digest ( 'SHA-256' , encode ( share + ':' + nonce ) )
115- const keyData = await crypto . subtle . digest ( 'SHA-256' , encode ( nonce + ':' + pwd ) )
116- const key = await crypto . subtle . importKey (
117- 'raw' , keyData , { name : 'AES-GCM' } , false , [ 'encrypt' , 'decrypt' ] ,
118- )
119-
120- return {
121- hash : btoa ( decode ( hash ) ) ,
122- nonce : nonce ,
123- encrypt : async function ( plaintext ) {
124- const cryptotext = await crypto . subtle . encrypt (
125- { name : 'AES-GCM' , iv : ivData . slice ( 0 , 12 ) , additionalData : encode ( nonce ) } ,
126- key , encode ( plaintext ) ,
127- )
128- return btoa ( decode ( cryptotext ) )
129- } ,
130- decrypt : async function ( cryptotext ) {
131- const plaintext = await crypto . subtle . decrypt (
132- { name : 'AES-GCM' , iv : ivData . slice ( 0 , 12 ) , additionalData : encode ( nonce ) } ,
133- key , encode ( atob ( cryptotext ) ) ,
134- )
135- return decode ( plaintext )
136- }
137- }
138- }
139- </ script >
140- < script >
141- async function connect ( share , pwd , media , tracker ) {
142- const crypto = await cipher ( share , pwd )
143- const pc = await PeerConnection ( media || 'video+audio' )
144- const offer = await crypto . encrypt ( await getOffer ( pc ) )
145-
146- const ws = new WebSocket ( tracker || 'wss://tracker.openwebtorrent.com/' )
147- ws . addEventListener ( 'open' , ( ) => {
148- ws . send ( JSON . stringify ( {
149- action : 'announce' ,
150- info_hash : crypto . hash ,
151- peer_id : Math . random ( ) . toString ( 36 ) . substring ( 2 ) ,
152- offers : [ {
153- offer_id : crypto . nonce ,
154- offer : { type : 'offer' , sdp : offer } ,
155- } ] ,
156- numwant : 1 ,
157- } ) )
158- } )
159-
160- ws . addEventListener ( 'message' , async ( ev ) => {
161- const msg = JSON . parse ( ev . data )
162- if ( ! msg . answer ) return
163-
164- const answer = await crypto . decrypt ( msg . answer . sdp )
165- await pc . setRemoteDescription ( { type : 'answer' , sdp : answer } )
166-
167- ws . close ( )
168- } )
169- }
170-
171- document . getElementById ( 'connect' ) . addEventListener ( 'click' , ( ) => {
172- const share = document . getElementById ( 'share' ) . value
173- const pwd = document . getElementById ( 'pwd' ) . value
174- connect ( share , pwd )
175- document . getElementById ( 'login' ) . style . display = 'none'
176- } )
177-
178- if ( location . hash ) {
179- const params = new URLSearchParams ( location . hash . substring ( 1 ) )
180- const share = params . get ( 'share' )
181- const pwd = params . get ( 'pwd' )
182- const media = params . get ( 'media' )
183- const tracker = params . get ( 'tr' )
184- connect ( share , pwd , media , tracker )
185- document . getElementById ( 'login' ) . style . display = 'none'
186- }
187- </ script >
25+ < img src ="https://raw.githubusercontent.com/AlexxIT/go2rtc/master/assets/logo.gif " alt ="go2rtc ">
26+ < a href ="https://github.com/AlexxIT/go2rtc "> github.com/AlexxIT/go2rtc</ a >
18827</ body >
189- </ html >
28+ </ html >
0 commit comments