mirror of
https://github.com/typecho/plugins.git
synced 2024-12-21 20:40:08 +08:00
Plugin SrsPlayer for live streaming
* Embed live streaming into a post/page or anywhere on your WordPress site * Supports HLS/HTTP-FLV/WebRTC over HTTP or HTTPS for live streaming * Embed HTML5 videos which are compatible with all major browsers * Automatically play a video when the page is rendered * No setup required, simply install and start embedding videos * Lightweight and compatible with the latest version of WordPress Please see https://github.com/ossrs/Typecho-Plugin-SrsPlayer
This commit is contained in:
parent
975251b0f8
commit
ae7188e2d1
174
SrsPlayer/Plugin.php
Normal file
174
SrsPlayer/Plugin.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
define( 'SRS_PLAYER_VERSION', '1.0.3' );
|
||||
|
||||
/**
|
||||
* SRS Player is a video streaming player, supports HLS/HTTP-FLV/WebRTC etc.
|
||||
*
|
||||
* @package SrsPlayer
|
||||
* @author Winlin Yang
|
||||
* @version 1.0.3
|
||||
* @link https://github.com/ossrs/Typecho-Plugin-SrsPlayer
|
||||
*/
|
||||
class SrsPlayer_Plugin implements Typecho_Plugin_Interface
|
||||
{
|
||||
public static function activate() {
|
||||
Typecho_Plugin::factory('Widget_Archive')->header = array(__CLASS__, 'header');
|
||||
Typecho_Plugin::factory('Widget_Archive')->footer = array(__CLASS__, 'footer');
|
||||
Typecho_Plugin::factory('Widget_Abstract_Contents')->content = array('SrsPlayer_Plugin', 'parse');
|
||||
}
|
||||
|
||||
public static function deactivate() {
|
||||
}
|
||||
|
||||
public static function config(Typecho_Widget_Helper_Form $form) {
|
||||
}
|
||||
|
||||
public static function personalConfig(Typecho_Widget_Helper_Form $form) {
|
||||
}
|
||||
|
||||
public static function render() {
|
||||
}
|
||||
|
||||
public static function header(){
|
||||
echo "\n";
|
||||
$cssUrl = Helper::options()->pluginUrl . '/SrsPlayer/public/css/srs-player-public.css?v=' . SRS_PLAYER_VERSION;
|
||||
echo '<link rel="stylesheet" href="' . $cssUrl . '">' . "\n";
|
||||
|
||||
// We must preload the jQuery and SrsPlayer.
|
||||
$urls = array(
|
||||
Helper::options()->pluginUrl . '/SrsPlayer/public/js/jquery-1.10.2.min.js?v=' . SRS_PLAYER_VERSION,
|
||||
Helper::options()->pluginUrl . '/SrsPlayer/public/js/srs.player.js?v=' . SRS_PLAYER_VERSION,
|
||||
);
|
||||
foreach ($urls as $url) {
|
||||
echo "<script src='${url}'></script>\n";
|
||||
}
|
||||
}
|
||||
|
||||
public static function footer() {
|
||||
// Lazy load after page is loaded.
|
||||
$urls = array(
|
||||
Helper::options()->pluginUrl . '/SrsPlayer/public/js/flv-1.5.0.min.js?v=' . SRS_PLAYER_VERSION,
|
||||
Helper::options()->pluginUrl . '/SrsPlayer/public/js/hls-0.14.17.min.js?v=' . SRS_PLAYER_VERSION,
|
||||
Helper::options()->pluginUrl . '/SrsPlayer/public/js/adapter-7.4.0.js?v=' . SRS_PLAYER_VERSION,
|
||||
Helper::options()->pluginUrl . '/SrsPlayer/public/js/srs.sdk.js?v=' . SRS_PLAYER_VERSION,
|
||||
);
|
||||
foreach ($urls as $url) {
|
||||
echo "<script src='${url}'></script>\n";
|
||||
}
|
||||
}
|
||||
|
||||
public static function parse($text, $widget, $lastResult) {
|
||||
if (!($widget instanceof Widget_Archive)) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$matches = array();
|
||||
preg_match_all('/\[(srs_player).*\]/', $text, $matches);
|
||||
if (empty($matches) || empty($matches[0])) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$o = $text;
|
||||
foreach ($matches[0] as $match) {
|
||||
// The $match is the player instance, for example,
|
||||
// [srs_player url='https://r.ossrs.net/live/livestream.m3u8' muted width="720"]
|
||||
// We parse $match to $obj as:
|
||||
// url: https://r.ossrs.net/live/livestream.m3u8
|
||||
// muted: muted
|
||||
// width: 720
|
||||
$obj = SrsPlayer_Plugin::toObject($match);
|
||||
|
||||
// Build $obj to $replace, the new H5 element.
|
||||
$replace = SrsPlayer_Plugin::buildReplacement($match, $obj);
|
||||
|
||||
// Replace the $match to the new H5 object.
|
||||
$o = str_replace($match, $replace, $o);
|
||||
}
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
||||
private static function toObject($match) {
|
||||
$input = trim(rtrim(ltrim(ltrim(ltrim($match, "[")), "srs_player"), "]"));
|
||||
|
||||
$attrs = array(
|
||||
'url' => '',
|
||||
'controls' => 'controls',
|
||||
'autoplay' => 'autoplay',
|
||||
'muted' => 'muted',
|
||||
'width' => '',
|
||||
);
|
||||
|
||||
foreach (explode(" ", $input) as $e) {
|
||||
$kv = explode("=", $e);
|
||||
if (count($kv) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$k = $kv[0];
|
||||
if (count($kv) == 1) {
|
||||
$attrs[$k] = $k;
|
||||
} else {
|
||||
$attrs[$k] = $kv[1];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attrs as $k => $v) {
|
||||
$attrs[$k] = trim(trim($v, '"'), "'");
|
||||
}
|
||||
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
private static function buildReplacement($match, $q) {
|
||||
if (empty($q)) {
|
||||
return "Invalid ${match}";
|
||||
}
|
||||
|
||||
if (empty($q['url'])) {
|
||||
return "No URL of ${match}";
|
||||
}
|
||||
|
||||
$url = $q['url'];
|
||||
$id = 'srs-player-' . SrsPlayer_Plugin::random_str(32);
|
||||
|
||||
$controls = ' controls=' . $q['controls'];
|
||||
if ($q['controls'] != 'true' && $q['controls'] != 'controls') $controls = '';
|
||||
|
||||
$autoplay = ' autoplay=' . $q['autoplay'];
|
||||
if ($q['autoplay'] != 'true' && $q['autoplay'] != 'autoplay') $autoplay = '';
|
||||
|
||||
$muted = ' muted=' . $q['muted'];
|
||||
if ($q['muted'] != 'true' && $q['muted'] != 'muted') $muted = '';
|
||||
|
||||
$width = ' width="' . $q['width'] . '"';
|
||||
if (empty($q['width'])) $width = '';
|
||||
|
||||
$o = <<<EOT
|
||||
<div class="srs-player-wrapper">
|
||||
<video id="${id}" ${controls}${autoplay}${muted}${width}></video>
|
||||
<script>(function($) { new SrsPlayer("#${id}", "${url}").play(); })(jQuery);</script>
|
||||
</div>
|
||||
EOT;
|
||||
return $o;
|
||||
}
|
||||
|
||||
private static function random_str($length, $keyspace = NULL) {
|
||||
if (empty($keyspace)) {
|
||||
$keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
}
|
||||
|
||||
if ($length < 1) {
|
||||
throw new RangeException("Invalid length ${length}");
|
||||
}
|
||||
|
||||
$pieces = array();
|
||||
$max = mb_strlen($keyspace, '8bit') - 1;
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$pieces []= $keyspace[rand(0, $max)];
|
||||
}
|
||||
return implode('', $pieces);
|
||||
}
|
||||
}
|
||||
|
6
SrsPlayer/public/css/srs-player-public.css
Normal file
6
SrsPlayer/public/css/srs-player-public.css
Normal file
@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Style for SRS Player
|
||||
*/
|
||||
.srs-player-wrapper {
|
||||
}
|
||||
|
3
SrsPlayer/public/index.php
Normal file
3
SrsPlayer/public/index.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
// Silence is golden
|
||||
|
5551
SrsPlayer/public/js/adapter-7.4.0.js
Normal file
5551
SrsPlayer/public/js/adapter-7.4.0.js
Normal file
File diff suppressed because it is too large
Load Diff
1
SrsPlayer/public/js/adapter-7.4.0.min.js
vendored
Normal file
1
SrsPlayer/public/js/adapter-7.4.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
7
SrsPlayer/public/js/flv-1.5.0.min.js
vendored
Normal file
7
SrsPlayer/public/js/flv-1.5.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
SrsPlayer/public/js/flv.min.js.map
Normal file
1
SrsPlayer/public/js/flv.min.js.map
Normal file
File diff suppressed because one or more lines are too long
2
SrsPlayer/public/js/hls-0.14.17.min.js
vendored
Normal file
2
SrsPlayer/public/js/hls-0.14.17.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
SrsPlayer/public/js/hls.min.js.map
Normal file
1
SrsPlayer/public/js/hls.min.js.map
Normal file
File diff suppressed because one or more lines are too long
6
SrsPlayer/public/js/jquery-1.10.2.min.js
vendored
Normal file
6
SrsPlayer/public/js/jquery-1.10.2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
SrsPlayer/public/js/jquery-1.10.2.min.map
Normal file
1
SrsPlayer/public/js/jquery-1.10.2.min.map
Normal file
File diff suppressed because one or more lines are too long
60
SrsPlayer/public/js/srs.player.js
Normal file
60
SrsPlayer/public/js/srs.player.js
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
(function($) {
|
||||
window.SrsPlayer = function (dom, url) {
|
||||
const self = {
|
||||
dom: $(dom),
|
||||
url,
|
||||
};
|
||||
|
||||
self.play = function() {
|
||||
if (document.readyState !== "complete") {
|
||||
return setTimeout(self.play, 0);
|
||||
}
|
||||
self.__play();
|
||||
}
|
||||
|
||||
self.__play = function () {
|
||||
if (self.url.indexOf('.mp4') > 0) {
|
||||
self.dom.prop('src', self.url);
|
||||
return console.log(`Play by native for ${self.url}`);
|
||||
}
|
||||
|
||||
if (self.url.indexOf('.flv') > 0) {
|
||||
if (!flvjs.isSupported()) return console.error(`HTTP-FLV is not supported by browser`);
|
||||
|
||||
const player = flvjs.createPlayer({type: 'flv', url: self.url});
|
||||
player.attachMediaElement(self.dom.get(0));
|
||||
player.load();
|
||||
player.play();
|
||||
return console.log(`Play by flv.js for ${self.url}`);
|
||||
}
|
||||
|
||||
if (self.url.indexOf('.m3u8') > 0) {
|
||||
// See https://stackoverflow.com/a/12905122/17679565
|
||||
if (document.createElement('video').canPlayType('application/vnd.apple.mpegURL')) {
|
||||
self.dom.prop('src', self.url);
|
||||
return console.log(`Play by native for ${self.url}`);
|
||||
}
|
||||
|
||||
const player = new Hls();
|
||||
player.loadSource(self.url);
|
||||
player.attachMedia(self.dom.get(0));
|
||||
return console.log(`Play by hls.js for ${self.url}`);
|
||||
}
|
||||
|
||||
if (self.url.indexOf('webrtc://') === 0) {
|
||||
const sdk = new SrsRtcPlayerAsync();
|
||||
self.dom.prop('srcObject', sdk.stream);
|
||||
sdk.play(self.url);
|
||||
return console.log(`Play by srs.sdk.js for ${self.url}`);
|
||||
}
|
||||
|
||||
console.error(`URL is not supported:${self.url}`);
|
||||
};
|
||||
|
||||
return self;
|
||||
};
|
||||
})(jQuery);
|
||||
|
508
SrsPlayer/public/js/srs.sdk.js
Normal file
508
SrsPlayer/public/js/srs.sdk.js
Normal file
@ -0,0 +1,508 @@
|
||||
|
||||
//
|
||||
// Copyright (c) 2013-2021 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() {
|
||||
var self = {};
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
|
||||
self.constraints = {
|
||||
audio: true,
|
||||
video: {
|
||||
width: {ideal: 320, max: 576}
|
||||
}
|
||||
};
|
||||
|
||||
// @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"});
|
||||
|
||||
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`);
|
||||
}
|
||||
var stream = await navigator.mediaDevices.getUserMedia(self.constraints);
|
||||
|
||||
// @see https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/addStream#Migrating_to_addTrack
|
||||
stream.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) 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;
|
||||
};
|
||||
|
||||
// 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 += '/';
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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"});
|
||||
|
||||
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) 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 += '/';
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user