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:
winlin 2022-04-14 12:59:50 +08:00
parent 975251b0f8
commit ae7188e2d1
13 changed files with 6321 additions and 0 deletions

174
SrsPlayer/Plugin.php Normal file
View 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);
}
}

View File

@ -0,0 +1,6 @@
/**
* Style for SRS Player
*/
.srs-player-wrapper {
}

View File

@ -0,0 +1,3 @@
<?php
// Silence is golden

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

7
SrsPlayer/public/js/flv-1.5.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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);

View 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;
}