diff --git a/trunk/3rdparty/signaling/main.go b/trunk/3rdparty/signaling/main.go index a3969200a..8e3b2ea2d 100644 --- a/trunk/3rdparty/signaling/main.go +++ b/trunk/3rdparty/signaling/main.go @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) 2025 Winlin +// # Copyright (c) 2025 Winlin // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go index 257bc3ccd..d64470404 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/errors.go @@ -2,88 +2,88 @@ // // The traditional error handling idiom in Go is roughly akin to // -// if err != nil { -// return err -// } +// if err != nil { +// return err +// } // // which applied recursively up the call stack results in error reports // without context or debugging information. The errors package allows // programmers to add context to the failure path in their code in a way // that does not destroy the original value of the error. // -// Adding context to an error +// # Adding context to an error // // The errors.Wrap function returns a new error that adds context to the // original error by recording a stack trace at the point Wrap is called, // and the supplied message. For example // -// _, err := ioutil.ReadAll(r) -// if err != nil { -// return errors.Wrap(err, "read failed") -// } +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } // // If additional control is required the errors.WithStack and errors.WithMessage // functions destructure errors.Wrap into its component operations of annotating // an error with a stack trace and an a message, respectively. // -// Retrieving the cause of an error +// # Retrieving the cause of an error // // Using errors.Wrap constructs a stack of errors, adding context to the // preceding error. Depending on the nature of the error it may be necessary // to reverse the operation of errors.Wrap to retrieve the original error // for inspection. Any error value which implements this interface // -// type causer interface { -// Cause() error -// } +// type causer interface { +// Cause() error +// } // // can be inspected by errors.Cause. errors.Cause will recursively retrieve // the topmost error which does not implement causer, which is assumed to be // the original cause. For example: // -// switch err := errors.Cause(err).(type) { -// case *MyError: -// // handle specifically -// default: -// // unknown error -// } +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } // // causer interface is not exported by this package, but is considered a part // of stable public API. // -// Formatted printing of errors +// # Formatted printing of errors // // All error values returned from this package implement fmt.Formatter and can // be formatted by the fmt package. The following verbs are supported // -// %s print the error. If the error has a Cause it will be -// printed recursively -// %v see %s -// %+v extended format. Each Frame of the error's StackTrace will -// be printed in detail. +// %s print the error. If the error has a Cause it will be +// printed recursively +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. // -// Retrieving the stack trace of an error or wrapper +// # Retrieving the stack trace of an error or wrapper // // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are // invoked. This information can be retrieved with the following interface. // -// type stackTracer interface { -// StackTrace() errors.StackTrace -// } +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } // // Where errors.StackTrace is defined as // -// type StackTrace []Frame +// type StackTrace []Frame // // The Frame type represents a call site in the stack trace. Frame supports // the fmt.Formatter interface that can be used for printing information about // the stack trace of this error. For example: // -// if err, ok := err.(stackTracer); ok { -// for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) -// } -// } +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d", f) +// } +// } // // stackTracer interface is not exported by this package, but is considered a part // of stable public API. @@ -247,9 +247,9 @@ func (w *withMessage) Format(s fmt.State, verb rune) { // An error value has a cause if it implements the following // interface: // -// type causer interface { -// Cause() error -// } +// type causer interface { +// Cause() error +// } // // If the error does not implement Cause, the original error will // be returned. If the error is nil, nil will be returned without further diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go index 6c42db5a8..7e5aacc48 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/errors/stack.go @@ -40,15 +40,15 @@ func (f Frame) line() int { // Format formats the frame according to the fmt.Formatter interface. // -// %s source file -// %d source line -// %n function name -// %v equivalent to %s:%d +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+s path of source file relative to the compile time GOPATH -// %+v equivalent to %+s:%d +// %+s path of source file relative to the compile time GOPATH +// %+v equivalent to %+s:%d func (f Frame) Format(s fmt.State, verb rune) { switch verb { case 's': @@ -82,12 +82,12 @@ type StackTrace []Frame // Format formats the stack of Frames according to the fmt.Formatter interface. // -// %s lists source files for each Frame in the stack -// %v lists the source file and line number for each Frame in the stack +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack // // Format accepts flags that alter the printing of some verbs, as follows: // -// %+v Prints filename, function, and line number for each Frame in the stack. +// %+v Prints filename, function, and line number for each Frame in the stack. func (st StackTrace) Format(s fmt.State, verb rune) { switch verb { case 'v': diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go index 65bdeb76f..917f571a0 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/go17.go @@ -19,6 +19,7 @@ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//go:build go1.7 // +build go1.7 package logger diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go index 61cea362f..90b22fc35 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/logger.go @@ -20,18 +20,23 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // The oryx logger package provides connection-oriented log service. -// logger.I(ctx, ...) -// logger.T(ctx, ...) -// logger.W(ctx, ...) -// logger.E(ctx, ...) +// +// logger.I(ctx, ...) +// logger.T(ctx, ...) +// logger.W(ctx, ...) +// logger.E(ctx, ...) +// // Or use format: -// logger.If(ctx, format, ...) -// logger.Tf(ctx, format, ...) -// logger.Wf(ctx, format, ...) -// logger.Ef(ctx, format, ...) +// +// logger.If(ctx, format, ...) +// logger.Tf(ctx, format, ...) +// logger.Wf(ctx, format, ...) +// logger.Ef(ctx, format, ...) +// // @remark the Context is optional thus can be nil. // @remark From 1.7+, the ctx could be context.Context, wrap by logger.WithContext, -// please read ExampleLogger_ContextGO17(). +// +// please read ExampleLogger_ContextGO17(). package logger import ( diff --git a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go index 24041dc88..1c46e8dde 100644 --- a/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go +++ b/trunk/3rdparty/signaling/vendor/github.com/ossrs/go-oryx-lib/logger/pre_go17.go @@ -19,6 +19,7 @@ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//go:build !go1.7 // +build !go1.7 package logger diff --git a/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go b/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go index 6c45c7352..ea422e110 100644 --- a/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go +++ b/trunk/3rdparty/signaling/vendor/golang.org/x/net/websocket/websocket.go @@ -8,8 +8,8 @@ // This package currently lacks some features found in alternative // and more actively maintained WebSocket packages: // -// https://godoc.org/github.com/gorilla/websocket -// https://godoc.org/nhooyr.io/websocket +// https://godoc.org/github.com/gorilla/websocket +// https://godoc.org/nhooyr.io/websocket package websocket // import "golang.org/x/net/websocket" import ( @@ -416,7 +416,6 @@ Trivial usage: // send binary frame data = []byte{0, 1, 2} websocket.Message.Send(ws, data) - */ var Message = Codec{marshal, unmarshal} diff --git a/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js b/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js index 6895807e4..34673c948 100644 --- a/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js +++ b/trunk/3rdparty/signaling/www/demos/js/srs.sdk.js @@ -1,32 +1,23 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2013-2025 Winlin - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ +// +// Copyright (c) 2013-2025 Winlin +// +// SPDX-License-Identifier: MIT +// 'use strict'; +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; + // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter -// Async-awat-prmise based SRS RTC Publisher. -function SrsRtcPublisherAsync() { +// Async-awat-prmise based SRS RTC Publisher by WHIP. +function SrsRtcWhipWhepAsync() { var self = {}; // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia @@ -37,80 +28,144 @@ function SrsRtcPublisherAsync() { } }; - // @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 - // or set the candidate(ip) of answer: - // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185 - // 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"}); + // Store media streams to stop tracks when closing. + self.displayStream = null; + self.userStream = null; - var stream = await navigator.mediaDevices.getUserMedia(self.constraints); + // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ + // @url The WebRTC url to publish with, for example: + // http://localhost:1985/rtc/v1/whip/?app=live&stream=livestream + // @options The options to control playing, supports: + // camera: boolean, whether capture video from camera, default to true. + // screen: boolean, whether capture video from screen, default to false. + // audio: boolean, whether play audio, default to true. + self.publish = async function (url, options) { + if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${url}`); + const hasAudio = options?.audio ?? true; + const useCamera = options?.camera ?? true; + const useScreen = options?.screen ?? false; - // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - stream.getTracks().forEach(function (track) { - self.pc.addTrack(track); + if (!hasAudio && !useCamera && !useScreen) throw new Error(`The camera, screen and audio can't be false at the same time`); - // Notify about local track when stream is ok. - self.ontrack && self.ontrack({track: track}); - }); + if (hasAudio) { + self.pc.addTransceiver("audio", {direction: "sendonly"}); + } else { + self.constraints.audio = false; + } + + if (useCamera || useScreen) { + self.pc.addTransceiver("video", {direction: "sendonly"}); + } + + if (!useCamera) { + self.constraints.video = false; + } + + if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { + throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); + } + + if (useScreen) { + self.displayStream = await navigator.mediaDevices.getDisplayMedia({ + video: true + }); + // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack + self.displayStream.getTracks().forEach(function (track) { + self.pc.addTrack(track); + // Notify about local track when stream is ok. + self.ontrack && self.ontrack({track: track}); + }); + } + + if (useCamera || hasAudio) { + self.userStream = await navigator.mediaDevices.getUserMedia(self.constraints); + + self.userStream.getTracks().forEach(function (track) { + self.pc.addTrack(track); + // Notify about local track when stream is ok. + self.ontrack && self.ontrack({track: track}); + }); + } 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 = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); + const answer = await new Promise(function (resolve, reject) { + console.log(`Generated offer: ${offer.sdp}`); - $.ajax({ - type: "POST", url: conf.apiUrl, data: JSON.stringify(data), - contentType: 'application/json', dataType: 'json' - }).done(function (data) { + const xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.readyState !== xhr.DONE) return; + if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); + const data = xhr.responseText; console.log("Got answer: ", data); - if (data.code) { - reject(data); - return; - } - - resolve(data); - }).fail(function (reason) { - reject(reason); - }); + return data.code ? reject(xhr) : resolve(data); + } + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-type', 'application/sdp'); + xhr.send(offer.sdp); }); await self.pc.setRemoteDescription( - new RTCSessionDescription({type: 'answer', sdp: session.sdp}) + new RTCSessionDescription({type: 'answer', sdp: answer}) ); - session.simulator = conf.schema + '//' + conf.urlObject.server + ':' + conf.port + '/rtc/v1/nack/'; - return session; + return self.__internal.parseId(url, offer.sdp, answer); + }; + + // See https://datatracker.ietf.org/doc/draft-ietf-wish-whip/ + // @url The WebRTC url to play with, for example: + // http://localhost:1985/rtc/v1/whep/?app=live&stream=livestream + // @options The options to control playing, supports: + // videoOnly: boolean, whether only play video, default to false. + // audioOnly: boolean, whether only play audio, default to false. + self.play = async function(url, options) { + if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`); + if (options?.videoOnly && options?.audioOnly) throw new Error(`The videoOnly and audioOnly in options can't be true at the same time`); + + if (!options?.videoOnly) self.pc.addTransceiver("audio", {direction: "recvonly"}); + if (!options?.audioOnly) self.pc.addTransceiver("video", {direction: "recvonly"}); + + var offer = await self.pc.createOffer(); + await self.pc.setLocalDescription(offer); + const answer = await new Promise(function(resolve, reject) { + console.log(`Generated offer: ${offer.sdp}`); + + const xhr = new XMLHttpRequest(); + xhr.onload = function() { + if (xhr.readyState !== xhr.DONE) return; + if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); + const data = xhr.responseText; + console.log("Got answer: ", data); + return data.code ? reject(xhr) : resolve(data); + } + xhr.open('POST', url, true); + xhr.setRequestHeader('Content-type', 'application/sdp'); + xhr.send(offer.sdp); + }); + await self.pc.setRemoteDescription( + new RTCSessionDescription({type: 'answer', sdp: answer}) + ); + + return self.__internal.parseId(url, offer.sdp, answer); }; // Close the publisher. self.close = function () { self.pc && self.pc.close(); self.pc = null; + + // Stop all media tracks to release camera/microphone. + if (self.displayStream) { + self.displayStream.getTracks().forEach(function (track) { + track.stop(); + }); + self.displayStream = null; + } + if (self.userStream) { + self.userStream.getTracks().forEach(function (track) { + track.stop(); + }); + self.userStream = null; + } }; // The callback when got local stream. @@ -120,147 +175,6 @@ function SrsRtcPublisherAsync() { self.stream.addTrack(event.track); }; - // 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; - - return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) - }; - }, - 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; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // 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) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - 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); // To keep api consistent between player and publisher. @@ -268,231 +182,23 @@ function SrsRtcPublisherAsync() { // @see https://webrtc.org/getting-started/media-devices self.stream = new MediaStream(); - 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 - // 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 - // or set the candidate(ip) of answer: - // webrtc://r.ossrs.net/live/livestream?eip=39.107.238.185 - // 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 = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - 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 player. - self.close = function() { - self.pc && self.pc.close(); - self.pc = null; - }; - - // 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); - }; - // 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; + parseId: (url, offer, answer) => { + let sessionid = offer.substr(offer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); + sessionid = sessionid.substr(0, sessionid.indexOf('\n') - 1) + ':'; + sessionid += answer.substr(answer.indexOf('a=ice-ufrag:') + 'a=ice-ufrag:'.length); + sessionid = sessionid.substr(0, sessionid.indexOf('\n')); + const a = document.createElement("a"); + a.href = url; return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) + sessionid: sessionid, // Should be ice-ufrag of answer:offer. + simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/', }; }, - 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; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // 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) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - 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); - - // 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) { @@ -503,33 +209,29 @@ function SrsRtcPlayerAsync() { 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) { +// https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport +function SrsRtcFormatStats(stats, kind) { var codecs = []; - senders.forEach(function (sender) { - var params = sender.getParameters(); - params && params.codecs && params.codecs.forEach(function(c) { - if (kind && sender.track.kind !== kind) { - return; - } - - if (c.mimeType.indexOf('/red') > 0 || c.mimeType.indexOf('/rtx') > 0 || c.mimeType.indexOf('/fec') > 0) { - return; - } - + stats.forEach((report) => { + if (report.type === 'codec' && report.mimeType?.toLowerCase().startsWith(kind)) { var s = ''; - s += c.mimeType.replace('audio/', '').replace('video/', ''); - s += ', ' + c.clockRate + 'HZ'; - if (sender.track.kind === "audio") { - s += ', channels: ' + c.channels; + s += report.mimeType.split('/')[1] || report.mimeType; + + if (report.clockRate) { + s += ', ' + report.clockRate + 'HZ'; } - s += ', pt: ' + c.payloadType; + if (kind === 'audio' && report.channels) { + s += ', channels: ' + report.channels; + } + + if (report.payloadType) { + s += ', pt: ' + report.payloadType; + } + codecs.push(s); - }); + } }); return codecs.join(", "); -} - +} \ No newline at end of file diff --git a/trunk/3rdparty/signaling/www/demos/one2one.html b/trunk/3rdparty/signaling/www/demos/one2one.html index 033980103..f7d03fbc1 100644 --- a/trunk/3rdparty/signaling/www/demos/one2one.html +++ b/trunk/3rdparty/signaling/www/demos/one2one.html @@ -213,23 +213,33 @@ } }; + // Convert webrtc:// URL to WHIP URL + var convertToWhipUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whip/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPublish = function (host, room, display) { $(".ff_first").each(function(i,e) { $(e).text(display); }); - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whipUrl = convertToWhipUrl(host, room, display); $('#rtc_media_publisher').show(); $('#publisher').show(); if (publisher) { publisher.close(); } - publisher = new SrsRtcPublisherAsync(); + publisher = new SrsRtcWhipWhepAsync(); $('#rtc_media_publisher').prop('srcObject', publisher.stream); - return publisher.publish(url).then(function(session){ - $('#self').text('Self: ' + url); + return publisher.publish(whipUrl).then(function(session){ + $('#self').text('Self: ' + display); }).catch(function (reason) { publisher.close(); $('#rtc_media_publisher').hide(); @@ -237,12 +247,22 @@ }); }; + // Convert webrtc:// URL to WHEP URL + var convertToWhepUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whep/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPlay = function (host, room, display) { $(".ff_second").each(function(i,e) { $(e).text(display); }); - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whepUrl = convertToWhepUrl(host, room, display); $('#rtc_media_player').show(); $('#player').show(); @@ -250,10 +270,10 @@ player.close(); } - player = new SrsRtcPlayerAsync(); + player = new SrsRtcWhipWhepAsync(); $('#rtc_media_player').prop('srcObject', player.stream); - player.play(url).then(function(session){ + player.play(whepUrl).then(function(session){ $('#peer').text('Peer: ' + display); $('#rtc_media_player').prop('muted', false); }).catch(function (reason) { diff --git a/trunk/3rdparty/signaling/www/demos/room.html b/trunk/3rdparty/signaling/www/demos/room.html index 5c67bf271..956876de0 100644 --- a/trunk/3rdparty/signaling/www/demos/room.html +++ b/trunk/3rdparty/signaling/www/demos/room.html @@ -126,23 +126,33 @@ }); }; + // Convert webrtc:// URL to WHIP URL + var convertToWhipUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whip/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPublish = function (host, room, display) { $(".ff_first").each(function(i,e) { $(e).text(display); }); - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whipUrl = convertToWhipUrl(host, room, display); $('#rtc_media_publisher').show(); $('#publisher').show(); if (publisher) { publisher.close(); } - publisher = new SrsRtcPublisherAsync(); + publisher = new SrsRtcWhipWhepAsync(); $('#rtc_media_publisher').prop('srcObject', publisher.stream); - return publisher.publish(url).then(function(session){ - $('#self').text('Self: ' + url); + return publisher.publish(whipUrl).then(function(session){ + $('#self').text('Self: ' + display); }).catch(function (reason) { publisher.close(); $('#rtc_media_publisher').hide(); @@ -150,6 +160,16 @@ }); }; + // Convert webrtc:// URL to WHEP URL + var convertToWhepUrl = function(host, room, display) { + var schema = window.location.protocol; + var port = 1985; + if (schema === 'https:') { + port = 443; + } + return schema + '//' + host + ':' + port + '/rtc/v1/whep/?app=' + room + '&stream=' + display + conf.query.replace('?', '&'); + }; + var startPlay = function (host, room, display) { $(".ff_second").each(function(i,e) { $(e).text(display); @@ -165,20 +185,20 @@ let ui = $('#player').clone().attr('id', 'player-' + display); let video = ui.children('#rtc_media_player'); console.log(video.length); - let player = new SrsRtcPlayerAsync(); + let player = new SrsRtcWhipWhepAsync(); players[display] = {ui:ui, video:video, player:player}; $('.srs_players').append(ui); // Start play for this user. - var url = 'webrtc://' + host + '/' + room + '/' + display + conf.query; + var whepUrl = convertToWhepUrl(host, room, display); video.show(); ui.show(); video.prop('srcObject', player.stream); - player.play(url).then(function(session){ - ui.children('#peer').text('Peer: ' + url); + player.play(whepUrl).then(function(session){ + ui.children('#peer').text('Peer: ' + display); video.prop('muted', false); }).catch(function (reason) { player.close(); diff --git a/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go b/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go index 5c950d070..43a559085 100644 --- a/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/blackbox_test.go @@ -21,12 +21,13 @@ package blackbox import ( - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "math/rand" "os" "testing" "time" + + "github.com/ossrs/go-oryx-lib/logger" ) func TestMain(m *testing.M) { diff --git a/trunk/3rdparty/srs-bench/blackbox/dvr_test.go b/trunk/3rdparty/srs-bench/blackbox/dvr_test.go index 5cf199660..6cef7ee8f 100644 --- a/trunk/3rdparty/srs-bench/blackbox/dvr_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/dvr_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_DvrFlv_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/hevc_test.go b/trunk/3rdparty/srs-bench/blackbox/hevc_test.go index 892c6643c..12de11573 100644 --- a/trunk/3rdparty/srs-bench/blackbox/hevc_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/hevc_test.go @@ -23,8 +23,6 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" @@ -32,6 +30,9 @@ import ( "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestSlow_RtmpPublish_RtmpPlay_HEVC_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/hls_test.go b/trunk/3rdparty/srs-bench/blackbox/hls_test.go index 395bf6da3..b2aa45768 100644 --- a/trunk/3rdparty/srs-bench/blackbox/hls_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/hls_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_HlsPlay_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/http_api_test.go b/trunk/3rdparty/srs-bench/blackbox/http_api_test.go index d81034a3c..f6d704c6c 100644 --- a/trunk/3rdparty/srs-bench/blackbox/http_api_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/http_api_test.go @@ -23,12 +23,13 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "net/http" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_Http_Api_Basic_Auth(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/mp3_test.go b/trunk/3rdparty/srs-bench/blackbox/mp3_test.go index 5fd9d2fb6..9507d9c82 100644 --- a/trunk/3rdparty/srs-bench/blackbox/mp3_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/mp3_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_RtmpPlay_CodecMP3_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go b/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go index c7c00ec1d..058d0c52f 100644 --- a/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/rtmp_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_RtmpPublish_RtmpPlay_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/srt_test.go b/trunk/3rdparty/srs-bench/blackbox/srt_test.go index 41f52c282..c9d2e9e2a 100644 --- a/trunk/3rdparty/srs-bench/blackbox/srt_test.go +++ b/trunk/3rdparty/srs-bench/blackbox/srt_test.go @@ -23,14 +23,15 @@ package blackbox import ( "context" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "math/rand" "os" "path" "sync" "testing" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) func TestFast_SrtPublish_SrtPlay_Basic(t *testing.T) { diff --git a/trunk/3rdparty/srs-bench/blackbox/util.go b/trunk/3rdparty/srs-bench/blackbox/util.go index d1c691e97..85da836c6 100644 --- a/trunk/3rdparty/srs-bench/blackbox/util.go +++ b/trunk/3rdparty/srs-bench/blackbox/util.go @@ -26,9 +26,6 @@ import ( "encoding/json" "flag" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - ohttp "github.com/ossrs/go-oryx-lib/http" - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "math/rand" "net/http" @@ -41,6 +38,10 @@ import ( "sync" "syscall" "time" + + "github.com/ossrs/go-oryx-lib/errors" + ohttp "github.com/ossrs/go-oryx-lib/http" + "github.com/ossrs/go-oryx-lib/logger" ) var srsLog *bool diff --git a/trunk/3rdparty/srs-bench/janus/api.go b/trunk/3rdparty/srs-bench/janus/api.go index 3c3317caf..3a534a53e 100644 --- a/trunk/3rdparty/srs-bench/janus/api.go +++ b/trunk/3rdparty/srs-bench/janus/api.go @@ -24,13 +24,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "net/http" "strings" "sync" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) type publisherInfo struct { diff --git a/trunk/3rdparty/srs-bench/janus/janus.go b/trunk/3rdparty/srs-bench/janus/janus.go index ee9b3d44a..62c5bf8c6 100644 --- a/trunk/3rdparty/srs-bench/janus/janus.go +++ b/trunk/3rdparty/srs-bench/janus/janus.go @@ -24,12 +24,13 @@ import ( "context" "flag" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "os" "strings" "sync" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) var sr string diff --git a/trunk/3rdparty/srs-bench/live/live.go b/trunk/3rdparty/srs-bench/live/live.go index 6dc3a8efe..af02afa3c 100644 --- a/trunk/3rdparty/srs-bench/live/live.go +++ b/trunk/3rdparty/srs-bench/live/live.go @@ -174,7 +174,7 @@ func Run(ctx context.Context) error { gStatLive.Publishers.Alive-- logger.Tf(ctx, "Publisher %v done, alive=%v", pr, gStatLive.Publishers.Alive) - <- publisherStartedCtx.Done() + <-publisherStartedCtx.Done() if gStatLive.Publishers.Alive == 0 { cancel() } diff --git a/trunk/3rdparty/srs-bench/srs/srs.go b/trunk/3rdparty/srs-bench/srs/srs.go index 931b459a6..eff8f004f 100644 --- a/trunk/3rdparty/srs-bench/srs/srs.go +++ b/trunk/3rdparty/srs-bench/srs/srs.go @@ -24,14 +24,15 @@ import ( "context" "flag" "fmt" - "github.com/ossrs/go-oryx-lib/errors" - "github.com/ossrs/go-oryx-lib/logger" "net" "net/http" "os" "strings" "sync" "time" + + "github.com/ossrs/go-oryx-lib/errors" + "github.com/ossrs/go-oryx-lib/logger" ) var sr, dumpAudio, dumpVideo string diff --git a/trunk/3rdparty/srs-bench/srs/srs_test.go b/trunk/3rdparty/srs-bench/srs/srs_test.go index 92edeac2e..1aca9502c 100644 --- a/trunk/3rdparty/srs-bench/srs/srs_test.go +++ b/trunk/3rdparty/srs-bench/srs/srs_test.go @@ -21,12 +21,13 @@ package srs import ( - "github.com/ossrs/go-oryx-lib/logger" "io/ioutil" "math/rand" "os" "testing" "time" + + "github.com/ossrs/go-oryx-lib/logger" ) func TestMain(m *testing.M) { diff --git a/trunk/research/players/js/srs.sdk.js b/trunk/research/players/js/srs.sdk.js index 4dadb445d..34673c948 100644 --- a/trunk/research/players/js/srs.sdk.js +++ b/trunk/research/players/js/srs.sdk.js @@ -15,512 +15,6 @@ function SrsError(name, message) { SrsError.prototype = Object.create(Error.prototype); SrsError.prototype.constructor = SrsError; -// 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 = {}; - - // https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia - self.constraints = { - audio: true, - video: { - width: {ideal: 320, max: 576} - } - }; - - // Store media stream to stop tracks when closing. - self.userStream = null; - - // @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 - // or set the candidate(eip) of answer: - // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185 - // 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"}); - //self.pc.addTransceiver("video", {direction: "sendonly"}); - //self.pc.addTransceiver("audio", {direction: "sendonly"}); - - if (!navigator.mediaDevices && window.location.protocol === 'http:' && window.location.hostname !== 'localhost') { - throw new SrsError('HttpsRequiredError', `Please use HTTPS or localhost to publish, read https://github.com/ossrs/srs/issues/2762#issuecomment-983147576`); - } - self.userStream = await navigator.mediaDevices.getUserMedia(self.constraints); - - // @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack - self.userStream.getTracks().forEach(function (track) { - self.pc.addTrack(track); - - // Notify about local track when stream is ok. - self.ontrack && self.ontrack({track: track}); - }); - - 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 = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - const xhr = new XMLHttpRequest(); - xhr.onload = function() { - if (xhr.readyState !== xhr.DONE) return; - if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); - const data = JSON.parse(xhr.responseText); - console.log("Got answer: ", data); - return data.code ? reject(xhr) : resolve(data); - } - xhr.open('POST', conf.apiUrl, true); - xhr.setRequestHeader('Content-type', 'application/json'); - xhr.send(JSON.stringify(data)); - }); - 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 () { - self.pc && self.pc.close(); - self.pc = null; - - // Stop all media tracks to release camera/microphone. - if (self.userStream) { - self.userStream.getTracks().forEach(function (track) { - track.stop(); - }); - self.userStream = null; - } - }; - - // The callback when got local stream. - // @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); - }; - - // 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 += '/'; - } - - var 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 - apiUrl = apiUrl.replace(api + '&', api + '?'); - - var streamUrl = urlObject.url; - - return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) - }; - }, - 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; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // 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) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - // 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. - 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); - - // 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(); - - 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 - // webrtc://r.ossrs.net:80/live/livestream - // 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 - // or set the candidate(eip) of answer: - // webrtc://r.ossrs.net/live/livestream?candidate=39.107.238.185 - // 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"}); - //self.pc.addTransceiver("video", {direction: "recvonly"}); - //self.pc.addTransceiver("audio", {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 = { - api: conf.apiUrl, tid: conf.tid, streamurl: conf.streamUrl, - clientip: null, sdp: offer.sdp - }; - console.log("Generated offer: ", data); - - const xhr = new XMLHttpRequest(); - xhr.onload = function() { - if (xhr.readyState !== xhr.DONE) return; - if (xhr.status !== 200 && xhr.status !== 201) return reject(xhr); - const data = JSON.parse(xhr.responseText); - console.log("Got answer: ", data); - return data.code ? reject(xhr) : resolve(data); - } - xhr.open('POST', conf.apiUrl, true); - xhr.setRequestHeader('Content-type', 'application/json'); - xhr.send(JSON.stringify(data)); - }); - 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 player. - self.close = function() { - self.pc && self.pc.close(); - self.pc = null; - }; - - // 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); - }; - - // 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 += '/'; - } - - var 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 - apiUrl = apiUrl.replace(api + '&', api + '?'); - - var streamUrl = urlObject.url; - - return { - apiUrl: apiUrl, streamUrl: streamUrl, schema: schema, urlObject: urlObject, port: port, - tid: Number(parseInt(new Date().getTime()*Math.random()*100)).toString(16).slice(0, 7) - }; - }, - 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; - var app = a.pathname.substring(1, a.pathname.lastIndexOf("/")); - var stream = a.pathname.slice(a.pathname.lastIndexOf("/") + 1); - - // parse the vhost in the params of app, that srs supports. - app = app.replace("...vhost...", "?vhost="); - if (app.indexOf("?") >= 0) { - var params = app.slice(app.indexOf("?")); - app = app.slice(0, app.indexOf("?")); - - if (params.indexOf("vhost=") > 0) { - vhost = params.slice(params.indexOf("vhost=") + "vhost=".length); - if (vhost.indexOf("&") > 0) { - vhost = vhost.slice(0, vhost.indexOf("&")); - } - } - } - - // 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) { - schema = url.slice(0, url.indexOf("://")); - } - - var port = a.port; - if (!port) { - // 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. - 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); - - // 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); - } - }; - - return self; -} - // Depends on adapter-7.4.0.min.js from https://github.com/webrtc/adapter // Async-awat-prmise based SRS RTC Publisher by WHIP. function SrsRtcWhipWhepAsync() { diff --git a/trunk/research/players/rtc_player.html b/trunk/research/players/rtc_player.html index e5a00308a..971f9fa77 100644 --- a/trunk/research/players/rtc_player.html +++ b/trunk/research/players/rtc_player.html @@ -67,6 +67,31 @@