2021-04-30 00:13:38 +00:00
2021-05-31 05:42:20 +00:00
//
// Copyright (c) 2013-2021 Winlin
//
// SPDX-License-Identifier: MIT
//
2021-04-30 00:13:38 +00:00
'use strict' ;
2022-02-11 00:44:31 +00:00
function SrsError ( name , message ) {
this . name = name ;
this . message = message ;
this . stack = ( new Error ( ) ) . stack ;
}
SrsError . prototype = Object . create ( Error . prototype ) ;
SrsError . prototype . constructor = SrsError ;
2021-04-30 00:13:38 +00:00
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-awat-prmise based SRS RTC Publisher.
function SrsRtcPublisherAsync ( ) {
var self = { } ;
2021-05-21 11:57:59 +00:00
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
self . constraints = {
audio : true ,
video : {
width : { ideal : 320 , max : 576 }
}
} ;
2021-04-30 00:13:38 +00:00
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
// or autostart the publish:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
2021-07-08 06:37:18 +00:00
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
2021-04-30 00:13:38 +00:00
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self . publish = async function ( url ) {
var conf = self . _ _internal . prepareUrl ( url ) ;
self . pc . addTransceiver ( "audio" , { direction : "sendonly" } ) ;
self . pc . addTransceiver ( "video" , { direction : "sendonly" } ) ;
2021-12-04 02:43:04 +00:00
if ( ! navigator . mediaDevices && window . location . protocol === 'http:' && window . location . hostname !== 'localhost' ) {
2022-02-11 00:44:31 +00:00
throw new SrsError ( 'HttpsRequiredError' , ` Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576 ` ) ;
2021-12-04 02:43:04 +00:00
}
2021-05-21 11:57:59 +00:00
var stream = await navigator . mediaDevices . getUserMedia ( self . constraints ) ;
2021-04-30 00:13:38 +00:00
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
stream . getTracks ( ) . forEach ( function ( track ) {
self . pc . addTrack ( track ) ;
2021-05-21 09:14:04 +00:00
// Notify about local track when stream is ok.
self . ontrack && self . ontrack ( { track : track } ) ;
2021-04-30 00:13:38 +00:00
} ) ;
var offer = await self . pc . createOffer ( ) ;
await self . pc . setLocalDescription ( offer ) ;
var session = await new Promise ( function ( resolve , reject ) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
2021-05-05 05:26:25 +00:00
api : conf . apiUrl , tid : conf . tid , streamurl : conf . streamUrl ,
clientip : null , sdp : offer . sdp
2021-04-30 00:13:38 +00:00
} ;
console . log ( "Generated offer: " , data ) ;
$ . ajax ( {
type : "POST" , url : conf . apiUrl , data : JSON . stringify ( data ) ,
contentType : 'application/json' , dataType : 'json'
} ) . done ( function ( data ) {
console . log ( "Got answer: " , data ) ;
if ( data . code ) {
reject ( data ) ;
return ;
}
resolve ( data ) ;
} ) . fail ( function ( reason ) {
reject ( reason ) ;
} ) ;
} ) ;
await self . pc . setRemoteDescription (
new RTCSessionDescription ( { type : 'answer' , sdp : session . sdp } )
) ;
session . simulator = conf . schema + '//' + conf . urlObject . server + ':' + conf . port + '/rtc/v1/nack/' ;
return session ;
} ;
// Close the publisher.
self . close = function ( ) {
2021-05-05 05:26:25 +00:00
self . pc && self . pc . close ( ) ;
2021-04-30 00:13:38 +00:00
self . pc = null ;
} ;
// The callback when got local stream.
2021-05-21 09:14:04 +00:00
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
self . ontrack = function ( event ) {
// Add track to stream of SDK.
self . stream . addTrack ( event . track ) ;
2021-04-30 00:13:38 +00:00
} ;
// Internal APIs.
self . _ _internal = {
defaultPath : '/rtc/v1/publish/' ,
prepareUrl : function ( webrtcUrl ) {
var urlObject = self . _ _internal . parse ( webrtcUrl ) ;
// If user specifies the schema, use it as API schema.
var schema = urlObject . user _query . schema ;
schema = schema ? schema + ':' : window . location . protocol ;
var port = urlObject . port || 1985 ;
if ( schema === 'https:' ) {
port = urlObject . port || 443 ;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject . user _query . play || self . _ _internal . defaultPath ;
if ( api . lastIndexOf ( '/' ) !== api . length - 1 ) {
api += '/' ;
}
apiUrl = schema + '//' + urlObject . server + ':' + port + api ;
for ( var key in urlObject . user _query ) {
if ( key !== 'api' && key !== 'play' ) {
apiUrl += '&' + key + '=' + urlObject . user _query [ key ] ;
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
var apiUrl = apiUrl . replace ( api + '&' , api + '?' ) ;
var streamUrl = urlObject . url ;
2021-05-05 05:26:25 +00:00
return {
apiUrl : apiUrl , streamUrl : streamUrl , schema : schema , urlObject : urlObject , port : port ,
2022-03-07 00:02:27 +00:00
tid : Number ( parseInt ( new Date ( ) . getTime ( ) * Math . random ( ) * 100 ) ) . toString ( 16 ) . slice ( 0 , 7 )
2021-05-05 05:26:25 +00:00
} ;
2021-04-30 00:13:38 +00:00
} ,
parse : function ( url ) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document . createElement ( "a" ) ;
a . href = url . replace ( "rtmp://" , "http://" )
. replace ( "webrtc://" , "http://" )
. replace ( "rtc://" , "http://" ) ;
var vhost = a . hostname ;
2022-03-07 00:02:27 +00:00
var app = a . pathname . substring ( 1 , a . pathname . lastIndexOf ( "/" ) ) ;
var stream = a . pathname . slice ( a . pathname . lastIndexOf ( "/" ) + 1 ) ;
2021-04-30 00:13:38 +00:00
// parse the vhost in the params of app, that srs supports.
app = app . replace ( "...vhost..." , "?vhost=" ) ;
if ( app . indexOf ( "?" ) >= 0 ) {
2022-03-07 00:02:27 +00:00
var params = app . slice ( app . indexOf ( "?" ) ) ;
app = app . slice ( 0 , app . indexOf ( "?" ) ) ;
2021-04-30 00:13:38 +00:00
if ( params . indexOf ( "vhost=" ) > 0 ) {
2022-03-07 00:02:27 +00:00
vhost = params . slice ( params . indexOf ( "vhost=" ) + "vhost=" . length ) ;
2021-04-30 00:13:38 +00:00
if ( vhost . indexOf ( "&" ) > 0 ) {
2022-03-07 00:02:27 +00:00
vhost = vhost . slice ( 0 , vhost . indexOf ( "&" ) ) ;
2021-04-30 00:13:38 +00:00
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if ( a . hostname === vhost ) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ ;
if ( re . test ( a . hostname ) ) {
vhost = "__defaultVhost__" ;
}
}
// parse the schema
var schema = "rtmp" ;
if ( url . indexOf ( "://" ) > 0 ) {
2022-03-07 00:02:27 +00:00
schema = url . slice ( 0 , url . indexOf ( "://" ) ) ;
2021-04-30 00:13:38 +00:00
}
var port = a . port ;
if ( ! port ) {
2022-01-13 10:26:28 +00:00
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if ( schema === 'webrtc' && url . indexOf ( ` webrtc:// ${ a . host } : ` ) === 0 ) {
port = ( url . indexOf ( ` webrtc:// ${ a . host } :80 ` ) === 0 ) ? 80 : 443 ;
}
// Guess by schema.
2021-04-30 00:13:38 +00:00
if ( schema === 'http' ) {
port = 80 ;
} else if ( schema === 'https' ) {
port = 443 ;
} else if ( schema === 'rtmp' ) {
port = 1935 ;
}
}
var ret = {
url : url ,
schema : schema ,
server : a . hostname , port : port ,
vhost : vhost , app : app , stream : stream
} ;
self . _ _internal . fill _query ( a . search , ret ) ;
// For webrtc API, we use 443 if page is https, or schema specified it.
if ( ! ret . port ) {
if ( schema === 'webrtc' || schema === 'rtc' ) {
if ( ret . user _query . schema === 'https' ) {
ret . port = 443 ;
} else if ( window . location . href . indexOf ( 'https://' ) === 0 ) {
ret . port = 443 ;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret . port = 1985 ;
}
}
}
return ret ;
} ,
fill _query : function ( query _string , obj ) {
// pure user query object.
obj . user _query = { } ;
if ( query _string . length === 0 ) {
return ;
}
// split again for angularjs.
if ( query _string . indexOf ( "?" ) >= 0 ) {
query _string = query _string . split ( "?" ) [ 1 ] ;
}
var queries = query _string . split ( "&" ) ;
for ( var i = 0 ; i < queries . length ; i ++ ) {
var elem = queries [ i ] ;
var query = elem . split ( "=" ) ;
obj [ query [ 0 ] ] = query [ 1 ] ;
obj . user _query [ query [ 0 ] ] = query [ 1 ] ;
}
// alias domain for vhost.
if ( obj . domain ) {
obj . vhost = obj . domain ;
}
}
} ;
self . pc = new RTCPeerConnection ( null ) ;
2021-05-21 09:14:04 +00:00
// To keep api consistent between player and publisher.
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
// @see https://webrtc.org/getting-started/media-devices
self . stream = new MediaStream ( ) ;
2021-04-30 00:13:38 +00:00
return self ;
}
// Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter
// Async-await-promise based SRS RTC Player.
function SrsRtcPlayerAsync ( ) {
var self = { } ;
// @see https://github.com/rtcdn/rtcdn-draft
// @url The WebRTC url to play with, for example:
// webrtc://r.ossrs.net/live/livestream
// or specifies the API port:
// webrtc://r.ossrs.net:11985/live/livestream
2022-01-13 10:26:28 +00:00
// webrtc://r.ossrs.net:80/live/livestream
2021-04-30 00:13:38 +00:00
// or autostart the play:
// webrtc://r.ossrs.net/live/livestream?autostart=true
// or change the app from live to myapp:
// webrtc://r.ossrs.net:11985/myapp/livestream
// or change the stream from livestream to mystream:
// webrtc://r.ossrs.net:11985/live/mystream
// or set the api server to myapi.domain.com:
// webrtc://myapi.domain.com/live/livestream
2021-07-08 06:37:18 +00:00
// or set the candidate(eip) of answer:
// webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185
2021-04-30 00:13:38 +00:00
// or force to access https API:
// webrtc://r.ossrs.net/live/livestream?schema=https
// or use plaintext, without SRTP:
// webrtc://r.ossrs.net/live/livestream?encrypt=false
// or any other information, will pass-by in the query:
// webrtc://r.ossrs.net/live/livestream?vhost=xxx
// webrtc://r.ossrs.net/live/livestream?token=xxx
self . play = async function ( url ) {
var conf = self . _ _internal . prepareUrl ( url ) ;
self . pc . addTransceiver ( "audio" , { direction : "recvonly" } ) ;
self . pc . addTransceiver ( "video" , { direction : "recvonly" } ) ;
var offer = await self . pc . createOffer ( ) ;
await self . pc . setLocalDescription ( offer ) ;
var session = await new Promise ( function ( resolve , reject ) {
// @see https://github.com/rtcdn/rtcdn-draft
var data = {
2021-05-05 05:26:25 +00:00
api : conf . apiUrl , tid : conf . tid , streamurl : conf . streamUrl ,
clientip : null , sdp : offer . sdp
2021-04-30 00:13:38 +00:00
} ;
console . log ( "Generated offer: " , data ) ;
$ . ajax ( {
type : "POST" , url : conf . apiUrl , data : JSON . stringify ( data ) ,
contentType : 'application/json' , dataType : 'json'
} ) . done ( function ( data ) {
console . log ( "Got answer: " , data ) ;
if ( data . code ) {
reject ( data ) ; return ;
}
resolve ( data ) ;
} ) . fail ( function ( reason ) {
reject ( reason ) ;
} ) ;
} ) ;
await self . pc . setRemoteDescription (
new RTCSessionDescription ( { type : 'answer' , sdp : session . sdp } )
) ;
2021-05-28 13:44:51 +00:00
session . simulator = conf . schema + '//' + conf . urlObject . server + ':' + conf . port + '/rtc/v1/nack/' ;
2021-04-30 00:13:38 +00:00
return session ;
} ;
// Close the player.
self . close = function ( ) {
2021-05-05 05:26:25 +00:00
self . pc && self . pc . close ( ) ;
2021-04-30 00:13:38 +00:00
self . pc = null ;
} ;
2021-05-21 09:14:04 +00:00
// The callback when got remote track.
// Note that the onaddstream is deprecated, @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/onaddstream
self . ontrack = function ( event ) {
// https://webrtc.org/getting-started/remote-streams
self . stream . addTrack ( event . track ) ;
} ;
2021-04-30 00:13:38 +00:00
// Internal APIs.
self . _ _internal = {
defaultPath : '/rtc/v1/play/' ,
prepareUrl : function ( webrtcUrl ) {
var urlObject = self . _ _internal . parse ( webrtcUrl ) ;
// If user specifies the schema, use it as API schema.
var schema = urlObject . user _query . schema ;
schema = schema ? schema + ':' : window . location . protocol ;
var port = urlObject . port || 1985 ;
if ( schema === 'https:' ) {
port = urlObject . port || 443 ;
}
// @see https://github.com/rtcdn/rtcdn-draft
var api = urlObject . user _query . play || self . _ _internal . defaultPath ;
if ( api . lastIndexOf ( '/' ) !== api . length - 1 ) {
api += '/' ;
}
apiUrl = schema + '//' + urlObject . server + ':' + port + api ;
for ( var key in urlObject . user _query ) {
if ( key !== 'api' && key !== 'play' ) {
apiUrl += '&' + key + '=' + urlObject . user _query [ key ] ;
}
}
// Replace /rtc/v1/play/&k=v to /rtc/v1/play/?k=v
var apiUrl = apiUrl . replace ( api + '&' , api + '?' ) ;
var streamUrl = urlObject . url ;
2021-05-05 05:26:25 +00:00
return {
apiUrl : apiUrl , streamUrl : streamUrl , schema : schema , urlObject : urlObject , port : port ,
2022-03-07 00:02:27 +00:00
tid : Number ( parseInt ( new Date ( ) . getTime ( ) * Math . random ( ) * 100 ) ) . toString ( 16 ) . slice ( 0 , 7 )
2021-05-05 05:26:25 +00:00
} ;
2021-04-30 00:13:38 +00:00
} ,
parse : function ( url ) {
// @see: http://stackoverflow.com/questions/10469575/how-to-use-location-object-to-parse-url-without-redirecting-the-page-in-javascri
var a = document . createElement ( "a" ) ;
a . href = url . replace ( "rtmp://" , "http://" )
. replace ( "webrtc://" , "http://" )
. replace ( "rtc://" , "http://" ) ;
var vhost = a . hostname ;
2022-03-07 00:02:27 +00:00
var app = a . pathname . substring ( 1 , a . pathname . lastIndexOf ( "/" ) ) ;
var stream = a . pathname . slice ( a . pathname . lastIndexOf ( "/" ) + 1 ) ;
2021-04-30 00:13:38 +00:00
// parse the vhost in the params of app, that srs supports.
app = app . replace ( "...vhost..." , "?vhost=" ) ;
if ( app . indexOf ( "?" ) >= 0 ) {
2022-03-07 00:02:27 +00:00
var params = app . slice ( app . indexOf ( "?" ) ) ;
app = app . slice ( 0 , app . indexOf ( "?" ) ) ;
2021-04-30 00:13:38 +00:00
if ( params . indexOf ( "vhost=" ) > 0 ) {
2022-03-07 00:02:27 +00:00
vhost = params . slice ( params . indexOf ( "vhost=" ) + "vhost=" . length ) ;
2021-04-30 00:13:38 +00:00
if ( vhost . indexOf ( "&" ) > 0 ) {
2022-03-07 00:02:27 +00:00
vhost = vhost . slice ( 0 , vhost . indexOf ( "&" ) ) ;
2021-04-30 00:13:38 +00:00
}
}
}
// when vhost equals to server, and server is ip,
// the vhost is __defaultVhost__
if ( a . hostname === vhost ) {
var re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ ;
if ( re . test ( a . hostname ) ) {
vhost = "__defaultVhost__" ;
}
}
// parse the schema
var schema = "rtmp" ;
if ( url . indexOf ( "://" ) > 0 ) {
2022-03-07 00:02:27 +00:00
schema = url . slice ( 0 , url . indexOf ( "://" ) ) ;
2021-04-30 00:13:38 +00:00
}
var port = a . port ;
if ( ! port ) {
2022-01-13 10:26:28 +00:00
// Finger out by webrtc url, if contains http or https port, to overwrite default 1985.
if ( schema === 'webrtc' && url . indexOf ( ` webrtc:// ${ a . host } : ` ) === 0 ) {
port = ( url . indexOf ( ` webrtc:// ${ a . host } :80 ` ) === 0 ) ? 80 : 443 ;
}
// Guess by schema.
2021-04-30 00:13:38 +00:00
if ( schema === 'http' ) {
port = 80 ;
} else if ( schema === 'https' ) {
port = 443 ;
} else if ( schema === 'rtmp' ) {
port = 1935 ;
}
}
var ret = {
url : url ,
schema : schema ,
server : a . hostname , port : port ,
vhost : vhost , app : app , stream : stream
} ;
self . _ _internal . fill _query ( a . search , ret ) ;
// For webrtc API, we use 443 if page is https, or schema specified it.
if ( ! ret . port ) {
if ( schema === 'webrtc' || schema === 'rtc' ) {
if ( ret . user _query . schema === 'https' ) {
ret . port = 443 ;
} else if ( window . location . href . indexOf ( 'https://' ) === 0 ) {
ret . port = 443 ;
} else {
// For WebRTC, SRS use 1985 as default API port.
ret . port = 1985 ;
}
}
}
return ret ;
} ,
fill _query : function ( query _string , obj ) {
// pure user query object.
obj . user _query = { } ;
if ( query _string . length === 0 ) {
return ;
}
// split again for angularjs.
if ( query _string . indexOf ( "?" ) >= 0 ) {
query _string = query _string . split ( "?" ) [ 1 ] ;
}
var queries = query _string . split ( "&" ) ;
for ( var i = 0 ; i < queries . length ; i ++ ) {
var elem = queries [ i ] ;
var query = elem . split ( "=" ) ;
obj [ query [ 0 ] ] = query [ 1 ] ;
obj . user _query [ query [ 0 ] ] = query [ 1 ] ;
}
// alias domain for vhost.
if ( obj . domain ) {
obj . vhost = obj . domain ;
}
}
} ;
self . pc = new RTCPeerConnection ( null ) ;
2021-05-21 09:14:04 +00:00
// Create a stream to add track to the stream, @see https://webrtc.org/getting-started/remote-streams
self . stream = new MediaStream ( ) ;
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self . pc . ontrack = function ( event ) {
if ( self . ontrack ) {
self . ontrack ( event ) ;
2021-04-30 00:13:38 +00:00
}
} ;
return self ;
}
// Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
function SrsRtcFormatSenders ( senders , kind ) {
var codecs = [ ] ;
senders . forEach ( function ( sender ) {
2021-05-21 11:57:59 +00:00
var params = sender . getParameters ( ) ;
params && params . codecs && params . codecs . forEach ( function ( c ) {
2021-04-30 00:13:38 +00:00
if ( kind && sender . track . kind !== kind ) {
return ;
}
if ( c . mimeType . indexOf ( '/red' ) > 0 || c . mimeType . indexOf ( '/rtx' ) > 0 || c . mimeType . indexOf ( '/fec' ) > 0 ) {
return ;
}
var s = '' ;
s += c . mimeType . replace ( 'audio/' , '' ) . replace ( 'video/' , '' ) ;
s += ', ' + c . clockRate + 'HZ' ;
if ( sender . track . kind === "audio" ) {
s += ', channels: ' + c . channels ;
}
s += ', pt: ' + c . payloadType ;
codecs . push ( s ) ;
} ) ;
} ) ;
return codecs . join ( ", " ) ;
}