【类 型】:feat

【原  因】:webrtc 视频流播放相关js
【过  程】:
【影  响】:

# 类型 包含:
# feat:新功能(feature)
# fix:修补bug
# docs:文档(documentation)
# style: 格式(不影响代码运行的变动)
# refactor:重构(即不是新增功能,也不是修改bug的代码变动)
# test:增加测试
# chore:构建过程或辅助工具的变动
This commit is contained in:
szdot 2025-06-08 19:35:03 +08:00
parent c6764c9e2e
commit a6527018c8
9 changed files with 1596 additions and 125 deletions

1
package-lock.json generated
View File

@ -7,6 +7,7 @@
"": {
"name": "food",
"version": "1.1.0",
"license": "ISC",
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",

View File

@ -40,5 +40,9 @@
"sass-loader": "^8.0.2",
"vue-router": "^3.6.5",
"vue-template-compiler": "^2.6.11"
}
},
"description": "```\r npm install\r ```",
"main": ".eslintrc.js",
"author": "",
"license": "ISC"
}

View File

@ -8,6 +8,13 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 声音api -->
<script src="https://code.responsivevoice.org/responsivevoice.js?key=i5XN3zfI"></script>
<script src="/js/srs/jquery-1.12.2.min.js"></script>
<script src="/js/srs/adapter-7.4.0.min.js"></script>
<script src="/js/srs/srs.sdk.js"></script>
<script src="/js/srs/winlin.utility.js"></script>
<script src="/js/srs/srs.page.js"></script>
<title>
<%= htmlWebpackPlugin.options.title %>
</title>

1
public/js/srs/adapter-7.4.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

5
public/js/srs/jquery-1.12.2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

182
public/js/srs/srs.page.js Normal file
View File

@ -0,0 +1,182 @@
// to query the swf anti cache.
function srs_get_version_code() { return "1.33"; }
/**
* player specified size.
*/
function srs_get_player_modal() { return 740; }
function srs_get_player_width() { return srs_get_player_modal() - 30; }
function srs_get_player_height() { return srs_get_player_width() * 9 / 19; }
/**
* update the navigator, add same query string.
*/
function update_nav() {
$("#nav_srs_player").attr("href", "srs_player.html" + window.location.search);
$("#nav_rtc_player").attr("href", "rtc_player.html" + window.location.search);
$("#nav_rtc_publisher").attr("href", "rtc_publisher.html" + window.location.search);
$("#nav_whip").attr("href", "whip.html" + window.location.search);
$("#nav_whep").attr("href", "whep.html" + window.location.search);
$("#nav_srs_publisher").attr("href", "srs_publisher.html" + window.location.search);
$("#nav_srs_chat").attr("href", "srs_chat.html" + window.location.search);
$("#nav_srs_bwt").attr("href", "srs_bwt.html" + window.location.search);
$("#nav_vlc").attr("href", "vlc.html" + window.location.search);
}
// Special extra params, such as auth_key.
function user_extra_params(query, params, rtc) {
var queries = params || [];
for (var key in query.user_query) {
if (key === 'app' || key === 'autostart' || key === 'dir'
|| key === 'filename' || key === 'host' || key === 'hostname'
|| key === 'http_port' || key === 'pathname' || key === 'port'
|| key === 'server' || key === 'stream' || key === 'buffer'
|| key === 'schema' || key === 'vhost' || key === 'api'
|| key === 'path'
) {
continue;
}
if (query[key]) {
queries.push(key + '=' + query[key]);
}
}
return queries;
}
function is_default_port(schema, port) {
return (schema === 'http' && port === 80)
|| (schema === 'https' && port === 443)
|| (schema === 'webrtc' && port === 1985)
|| (schema === 'rtmp' && port === 1935);
}
/**
@param server the ip of server. default to window.location.hostname
@param vhost the vhost of HTTP-FLV. default to window.location.hostname
@param port the port of HTTP-FLV. default to 1935
@param app the app of HTTP-FLV. default to live.
@param stream the stream of HTTP-FLV. default to livestream.flv
*/
function build_default_flv_url() {
var query = parse_query_string();
var schema = (!query.schema)? "http":query.schema;
var server = (!query.server)? window.location.hostname:query.server;
var port = (!query.port)? (schema==="http"? 8080:1935) : Number(query.port);
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
var app = (!query.app)? "live":query.app;
var stream = (!query.stream)? "livestream.flv":query.stream;
var queries = [];
if (server !== vhost && vhost !== "__defaultVhost__") {
queries.push("vhost=" + vhost);
}
queries = user_extra_params(query, queries);
var uri = schema + "://" + server;
if (!is_default_port(schema, port)) {
uri += ":" + port;
}
uri += "/" + app + "/" + stream + "?" + queries.join('&');
while (uri.indexOf("?") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
return uri;
}
function build_default_rtc_url(query) {
// The format for query string to overwrite configs of server.
console.log('?eip=x.x.x.x to overwrite candidate. 覆盖服务器candidate(外网IP)配置');
console.log('?api=x to overwrite WebRTC API(1985).');
console.log('?schema=http|https to overwrite WebRTC API protocol.');
var server = (!query.server)? window.location.hostname:query.server;
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
var app = (!query.app)? "live":query.app;
var stream = (!query.stream)? "livestream":query.stream;
var api = query.api? ':'+query.api : '';
var queries = [];
if (server !== vhost && vhost !== "__defaultVhost__") {
queries.push("vhost=" + vhost);
}
if (query.schema && window.location.protocol !== query.schema + ':') {
queries.push('schema=' + query.schema);
}
queries = user_extra_params(query, queries, true);
var uri = "webrtc://" + server + api + "/" + app + "/" + stream + "?" + queries.join('&');
while (uri.lastIndexOf("?") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
return uri;
};
function build_default_whip_whep_url(query, apiPath) {
// The format for query string to overwrite configs of server.
console.log('?eip=x.x.x.x to overwrite candidate. 覆盖服务器candidate(外网IP)配置');
console.log('?api=x to overwrite WebRTC API(1985).');
console.log('?schema=http|https to overwrite WebRTC API protocol.');
console.log(`?path=xxx to overwrite default ${apiPath}`);
var server = (!query.server)? window.location.hostname:query.server;
var vhost = (!query.vhost)? window.location.hostname:query.vhost;
var app = (!query.app)? "live":query.app;
var stream = (!query.stream)? "livestream":query.stream;
var api = ':' + (query.api || (window.location.protocol === 'http:' ? '1985' : '1990'));
const realApiPath = query.path || apiPath;
var queries = [];
if (server !== vhost && vhost !== "__defaultVhost__") {
queries.push("vhost=" + vhost);
}
if (query.schema && window.location.protocol !== query.schema + ':') {
queries.push('schema=' + query.schema);
}
queries = user_extra_params(query, queries, true);
var uri = window.location.protocol + "//" + server + api + realApiPath + "?app=" + app + "&stream=" + stream + "&" + queries.join('&');
while (uri.lastIndexOf("?") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
while (uri.lastIndexOf("&") === uri.length - 1) {
uri = uri.slice(0, uri.length - 1);
}
return uri;
}
/**
* initialize the page.
* @param flv_url the div id contains the flv stream url to play
* @param hls_url the div id contains the hls stream url to play
* @param modal_player the div id contains the modal player
*/
function srs_init_flv(flv_url, modal_player) {
update_nav();
if (flv_url) {
$(flv_url).val(build_default_flv_url());
}
if (modal_player) {
$(modal_player).width(srs_get_player_modal() + "px");
$(modal_player).css("margin-left", "-" + srs_get_player_modal() / 2 +"px");
}
}
function srs_init_rtc(id, query) {
update_nav();
$(id).val(build_default_rtc_url(query));
}
function srs_init_whip(id, query) {
update_nav();
$(id).val(build_default_whip_whep_url(query, '/rtc/v1/whip/'));
}
function srs_init_whep(id, query) {
update_nav();
$(id).val(build_default_whip_whep_url(query, '/rtc/v1/whep/'));
}

681
public/js/srs/srs.sdk.js Normal file
View File

@ -0,0 +1,681 @@
//
// 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"});
//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`);
}
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 && 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;
};
// 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() {
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://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
self.publish = async function (url) {
if (url.indexOf('/whip/') === -1) throw new Error(`invalid WHIP url ${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);
const answer = await new Promise(function (resolve, reject) {
console.log("Generated offer: ", offer);
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);
};
// 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
self.play = async function(url) {
if (url.indexOf('/whip-play/') === -1 && url.indexOf('/whep/') === -1) throw new Error(`invalid WHEP url ${url}`);
self.pc.addTransceiver("audio", {direction: "recvonly"});
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);
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;
};
// 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);
};
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();
// Internal APIs.
self.__internal = {
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 {
sessionid: sessionid, // Should be ice-ufrag of answer:offer.
simulator: a.protocol + '//' + a.host + '/rtc/v1/nack/',
};
},
};
// https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/ontrack
self.pc.ontrack = function(event) {
if (self.ontrack) {
self.ontrack(event);
}
};
return self;
}
// Format the codec of RTCRtpSender, kind(audio/video) is optional filter.
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#getting_the_supported_codecs
function SrsRtcFormatSenders(senders, kind) {
var codecs = [];
senders.forEach(function (sender) {
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;
}
var s = '';
s += c.mimeType.replace('audio/', '').replace('video/', '');
s += ', ' + c.clockRate + 'HZ';
if (sender.track.kind === "audio") {
s += ', channels: ' + c.channels;
}
s += ', pt: ' + c.payloadType;
codecs.push(s);
});
});
return codecs.join(", ");
}

View File

@ -0,0 +1,686 @@
// winlin.utility.js
/**
* common utilities
* depends: jquery1.10
* https://gitee.com/winlinvip/codes/rpn0c2ewbomj81augzk4y59
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* v 1.0.23
*/
/**
* padding the output.
* padding(3, 5, '0') is 00003
* padding(3, 5, 'x') is xxxx3
* @see http://blog.csdn.net/win_lin/article/details/12065413
*/
function padding(number, length, prefix) {
if(String(number).length >= length){
return String(number);
}
return padding(prefix+number, length, prefix);
}
/**
* extends system array, to remove all specified elem.
* @param arr the array to remove elem from.
* @param elem the elem to remove.
* @remark all elem will be removed.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_remove(arr, 10) // arr=[15, 20, 30, 20, 40]
* system_array_remove(arr, 20) // arr=[15, 30, 40]
*/
function system_array_remove(arr, elem) {
if (!arr) {
return;
}
var removed = true;
var i = 0;
while (removed) {
removed = false;
for (; i < arr.length; i++) {
if (elem == arr[i]) {
arr.splice(i, 1);
removed = true;
break;
}
}
}
}
/**
* whether the array contains specified element.
* @param arr the array to find.
* @param elem_or_function the element value or compare function.
* @returns true contains elem; otherwise false.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_contains(arr, 10) // true
* system_array_contains(arr, 11) // false
* system_array_contains(arr, function(elem){return elem == 30;}); // true
* system_array_contains(arr, function(elem){return elem == 60;}); // false
*/
function system_array_contains(arr, elem_or_function) {
return system_array_get(arr, elem_or_function) != null;
}
/**
* get the specified element from array
* @param arr the array to find.
* @param elem_or_function the element value or compare function.
* @returns the matched elem; otherwise null.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_get(arr, 10) // 10
* system_array_get(arr, 11) // null
* system_array_get(arr, function(elem){return elem == 30;}); // 30
* system_array_get(arr, function(elem){return elem == 60;}); // null
*/
function system_array_get(arr, elem_or_function) {
for (var i = 0; i < arr.length; i++) {
if (typeof elem_or_function == "function") {
if (elem_or_function(arr[i])) {
return arr[i];
}
} else {
if (elem_or_function == arr[i]) {
return arr[i];
}
}
}
return null;
}
/**
* to iterate on array.
* @param arr the array to iterate on.
* @param pfn the function to apply on it. return false to break loop.
* for example,
* arr = [10, 15, 20, 30, 20, 40]
* system_array_foreach(arr, function(elem, index){
* console.log('index=' + index + ',elem=' + elem);
* });
* @return true when iterate all elems.
*/
function system_array_foreach(arr, pfn) {
if (!pfn) {
return false;
}
for (var i = 0; i < arr.length; i++) {
if (!pfn(arr[i], i)) {
return false;
}
}
return true;
}
/**
* whether the str starts with flag.
*/
function system_string_startswith(str, flag) {
if (typeof flag == "object" && flag.constructor == Array) {
for (var i = 0; i < flag.length; i++) {
if (system_string_startswith(str, flag[i])) {
return true;
}
}
}
return str && flag && str.length >= flag.length && str.indexOf(flag) == 0;
}
/**
* whether the str ends with flag.
*/
function system_string_endswith(str, flag) {
if (typeof flag == "object" && flag.constructor == Array) {
for (var i = 0; i < flag.length; i++) {
if (system_string_endswith(str, flag[i])) {
return true;
}
}
}
return str && flag && str.length >= flag.length && str.indexOf(flag) == str.length - flag.length;
}
/**
* trim the start and end of flag in str.
* @param flag a string to trim.
*/
function system_string_trim(str, flag) {
if (!flag || !flag.length || typeof flag != "string") {
return str;
}
while (system_string_startswith(str, flag)) {
str = str.slice(flag.length);
}
while (system_string_endswith(str, flag)) {
str = str.slice(0, str.length - flag.length);
}
return str;
}
/**
* array sort asc, for example:
* [a, b] in [10, 11, 9]
* then sort to: [9, 10, 11]
* Usage, for example:
obj.data.data.sort(function(a, b){
return array_sort_asc(a.metadata.meta_id, b.metadata.meta_id);
});
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @remark, if need desc, use -1*array_sort_asc(a,b)
*/
function array_sort_asc(elem_a, elem_b) {
if (elem_a > elem_b) {
return 1;
}
return (elem_a < elem_b)? -1 : 0;
}
function array_sort_desc(elem_a, elem_b) {
return -1 * array_sort_asc(elem_a, elem_b);
}
function system_array_sort_asc(elem_a, elem_b) {
return array_sort_asc(elem_a, elem_b);
}
function system_array_sort_desc(elem_a, elem_b) {
return -1 * array_sort_asc(elem_a, elem_b);
}
/**
* parse the query string to object.
* parse the url location object as: host(hostname:http_port), pathname(dir/filename)
* for example, url http://192.168.1.168:1980/ui/players.html?vhost=player.vhost.com&app=test&stream=livestream
* parsed to object:
{
host : "192.168.1.168:1980",
hostname : "192.168.1.168",
http_port : 1980,
pathname : "/ui/players.html",
dir : "/ui",
filename : "/players.html",
vhost : "player.vhost.com",
app : "test",
stream : "livestream"
}
* @see: http://blog.csdn.net/win_lin/article/details/17994347
*/
function parse_query_string(){
var obj = {};
// add the uri object.
// parse the host(hostname:http_port), pathname(dir/filename)
obj.host = window.location.host;
obj.hostname = window.location.hostname;
obj.http_port = (window.location.port == "")? 80:window.location.port;
obj.pathname = window.location.pathname;
if (obj.pathname.lastIndexOf("/") <= 0) {
obj.dir = "/";
obj.filename = "";
} else {
obj.dir = obj.pathname.slice(0, obj.pathname.lastIndexOf("/"));
obj.filename = obj.pathname.slice(obj.pathname.lastIndexOf("/"));
}
// pure user query object.
obj.user_query = {};
// parse the query string.
var query_string = String(window.location.search).replace(" ", "").split("?")[1];
if(query_string === undefined){
query_string = String(window.location.hash).replace(" ", "").split("#")[1];
if(query_string === undefined){
return obj;
}
}
__fill_query(query_string, obj);
return obj;
}
function __fill_query(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;
}
}
/**
* parse the rtmp url,
* for example: rtmp://demo.srs.com:1935/live...vhost...players/livestream
* @return object {server, port, vhost, app, stream}
* for exmaple, rtmp_url is rtmp://demo.srs.com:1935/live...vhost...players/livestream
* parsed to object:
{
server: "demo.srs.com",
port: 1935,
vhost: "players",
app: "live",
stream: "livestream"
}
*/
function parse_rtmp_url(rtmp_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 = rtmp_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 (rtmp_url.indexOf("://") > 0) {
schema = rtmp_url.slice(0, rtmp_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: rtmp_url,
schema: schema,
server: a.hostname, port: port,
vhost: vhost, app: app, stream: stream
};
__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;
}
/**
* get the agent.
* @return an object specifies some browser.
* for example, get_browser_agents().MSIE
* @see: http://blog.csdn.net/win_lin/article/details/17994347
*/
function get_browser_agents() {
var agent = navigator.userAgent;
/**
WindowsPC platform, Win7:
chrome 31.0.1650.63:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
firefox 23.0.1:
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20100101
Firefox/23.0
safari 5.1.7(7534.57.2):
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.57.2
(KHTML, like Gecko) Version/5.1.7 Safari/534.57.2
opera 15.0.1147.153:
Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36
(KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
OPR/15.0.1147.153
360 6.2.1.272:
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
.NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
.NET4.0E)
IE 10.0.9200.16750(update: 10.0.12):
Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64;
Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729;
.NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET4.0C;
.NET4.0E)
*/
return {
// platform
Android: agent.indexOf("Android") != -1,
Windows: agent.indexOf("Windows") != -1,
iPhone: agent.indexOf("iPhone") != -1,
// Windows Browsers
Chrome: agent.indexOf("Chrome") != -1,
Firefox: agent.indexOf("Firefox") != -1,
QQBrowser: agent.indexOf("QQBrowser") != -1,
MSIE: agent.indexOf("MSIE") != -1,
// Android Browsers
Opera: agent.indexOf("Presto") != -1,
MQQBrowser: agent.indexOf("MQQBrowser") != -1
};
}
/**
* format relative seconds to HH:MM:SS,
* for example, 210s formated to 00:03:30
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @usage relative_seconds_to_HHMMSS(210)
*/
function relative_seconds_to_HHMMSS(seconds){
var date = new Date();
date.setTime(Number(seconds) * 1000);
var ret = padding(date.getUTCHours(), 2, '0')
+ ":" + padding(date.getUTCMinutes(), 2, '0')
+ ":" + padding(date.getUTCSeconds(), 2, '0');
return ret;
}
/**
* format absolute seconds to HH:MM:SS,
* for example, 1389146480s (2014-01-08 10:01:20 GMT+0800) formated to 10:01:20
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @usage absolute_seconds_to_HHMMSS(new Date().getTime() / 1000)
*/
function absolute_seconds_to_HHMMSS(seconds){
var date = new Date();
date.setTime(Number(seconds) * 1000);
var ret = padding(date.getHours(), 2, '0')
+ ":" + padding(date.getMinutes(), 2, '0')
+ ":" + padding(date.getSeconds(), 2, '0');
return ret;
}
/**
* format absolute seconds to YYYY-mm-dd,
* for example, 1389146480s (2014-01-08 10:01:20 GMT+0800) formated to 2014-01-08
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* @usage absolute_seconds_to_YYYYmmdd(new Date().getTime() / 1000)
*/
function absolute_seconds_to_YYYYmmdd(seconds) {
var date = new Date();
date.setTime(Number(seconds) * 1000);
var ret = date.getFullYear()
+ "-" + padding(date.getMonth() + 1, 2, '0')
+ "-" + padding(date.getDate(), 2, '0');
return ret;
}
/**
* parse the date in str to Date object.
* @param str the date in str, format as "YYYY-mm-dd", for example, 2014-12-11
* @returns a date object.
* @usage YYYYmmdd_parse("2014-12-11")
*/
function YYYYmmdd_parse(str) {
var date = new Date();
date.setTime(Date.parse(str));
return date;
}
/**
* async refresh function call. to avoid multiple call.
* @remark AsyncRefresh is for jquery to refresh the speicified pfn in a page;
* if angularjs, use AsyncRefresh2 to change pfn, cancel previous request for angularjs use singleton object.
* @param refresh_interval the default refresh interval ms.
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* the pfn can be implements as following:
var async_refresh = new AsyncRefresh(pfn, 3000);
function pfn() {
if (!async_refresh.refresh_is_enabled()) {
async_refresh.request(100);
return;
}
$.ajax({
type: 'GET', async: true, url: 'xxxxx',
complete: function(){
if (!async_refresh.refresh_is_enabled()) {
async_refresh.request(0);
} else {
async_refresh.request(async_refresh.refresh_interval);
}
},
success: function(res){
// if donot allow refresh, directly return.
if (!async_refresh.refresh_is_enabled()) {
return;
}
// render the res.
}
});
}
*/
function AsyncRefresh(pfn, refresh_interval) {
this.refresh_interval = refresh_interval;
this.__handler = null;
this.__pfn = pfn;
this.__enabled = true;
}
/**
* disable the refresher, the pfn must check the refresh state.
*/
AsyncRefresh.prototype.refresh_disable = function() {
this.__enabled = false;
}
AsyncRefresh.prototype.refresh_enable = function() {
this.__enabled = true;
}
AsyncRefresh.prototype.refresh_is_enabled = function() {
return this.__enabled;
}
/**
* start new async request
* @param timeout the timeout in ms.
* user can use the refresh_interval of the AsyncRefresh object,
* which initialized in constructor.
*/
AsyncRefresh.prototype.request = function(timeout) {
if (this.__handler) {
clearTimeout(this.__handler);
}
this.__handler = setTimeout(this.__pfn, timeout);
}
/**
* async refresh v2, support cancellable refresh, and change the refresh pfn.
* @remakr for angularjs. if user only need jquery, maybe AsyncRefresh is better.
* @see: http://blog.csdn.net/win_lin/article/details/17994347
* Usage:
bsmControllers.controller('CServers', ['$scope', 'MServer', function($scope, MServer){
async_refresh2.refresh_change(function(){
// 获取服务器列表
MServer.servers_load({}, function(data){
$scope.servers = data.data.servers;
async_refresh2.request();
});
}, 3000);
async_refresh2.request(0);
}]);
bsmControllers.controller('CStreams', ['$scope', 'MStream', function($scope, MStream){
async_refresh2.refresh_change(function(){
// 获取流列表
MStream.streams_load({}, function(data){
$scope.streams = data.data.streams;
async_refresh2.request();
});
}, 3000);
async_refresh2.request(0);
}]);
*/
function AsyncRefresh2() {
/**
* the function callback before call the pfn.
* the protype is function():bool, which return true to invoke, false to abort the call.
* null to ignore this callback.
*
* for example, user can abort the refresh by find the class popover:
* async_refresh2.on_before_call_pfn = function() {
* if ($(".popover").length > 0) {
* async_refresh2.request();
* return false;
* }
* return true;
* };
*/
this.on_before_call_pfn = null;
// use a anonymous function to call, and check the enabled when actually invoke.
this.__call = {
pfn: null,
timeout: 0,
__enabled: false,
__handler: null
};
}
// singleton
var async_refresh2 = new AsyncRefresh2();
/**
* initialize or refresh change. cancel previous request, setup new request.
* @param pfn a function():void to request after timeout. null to disable refresher.
* @param timeout the timeout in ms, to call pfn. null to disable refresher.
*/
AsyncRefresh2.prototype.initialize = function(pfn, timeout) {
this.refresh_change(pfn, timeout);
}
/**
* stop refresh, the refresh pfn is set to null.
*/
AsyncRefresh2.prototype.stop = function() {
this.__call.__enabled = false;
}
/**
* restart refresh, use previous config.
*/
AsyncRefresh2.prototype.restart = function() {
this.__call.__enabled = true;
this.request(0);
}
/**
* change refresh pfn, the old pfn will set to disabled.
*/
AsyncRefresh2.prototype.refresh_change = function(pfn, timeout) {
// cancel the previous call.
if (this.__call.__handler) {
clearTimeout(this.__handler);
}
this.__call.__enabled = false;
// setup new call.
this.__call = {
pfn: pfn,
timeout: timeout,
__enabled: true,
__handler: null
};
}
/**
* start new request, we never auto start the request,
* user must start new request when previous completed.
* @param timeout [optional] if not specified, use the timeout in initialize or refresh_change.
*/
AsyncRefresh2.prototype.request = function(timeout) {
var self = this;
var this_call = this.__call;
// clear previous timeout.
if (this_call.__handler) {
clearTimeout(this_call.__handler);
}
// override the timeout
if (timeout == undefined) {
timeout = this_call.timeout;
}
// if user disabled refresher.
if (this_call.pfn == null || timeout == null) {
return;
}
this_call.__handler = setTimeout(function(){
// cancelled by refresh_change, ignore.
if (!this_call.__enabled) {
return;
}
// callback if the handler installled.
if (self.on_before_call_pfn) {
if (!self.on_before_call_pfn()) {
return;
}
}
// do the actual call.
this_call.pfn();
}, timeout);
}

View File

@ -1,141 +1,45 @@
<template>
<div class="app-container">
<!-- 组合按钮 -->
<el-button-group>
<el-button type="primary" icon="el-icon-plus" @click="$router.replace('/site/add')">添加</el-button>
<el-button type="danger" icon="el-icon-delete" @click="deleteSite(countSelIdArr($refs.myTable.selection))">删除
</el-button>
<el-button type="warning" icon="el-icon-edit" @click="toEditPage()">编辑</el-button>
</el-button-group>
<!-- 用户select选项 -->
<el-button-group class="m-l-20">
<SelectionShopId v-model="form.shop_id" :allSel="true" />
</el-button-group>
<!-- 站点表格 -->
<el-table class="m-t-20 w-100" ref="myTable"
:data="siteListArr.slice((currentPage - 1) * pageSize, currentPage * pageSize)" border tooltip-effect="dark">
<el-table-column align="center" type="selection" width="40">
</el-table-column>
<el-table-column align="center" prop="id" label="id" width="50">
</el-table-column>
<el-table-column prop="sitename" label="站点名称" width="120" min-width="100">
</el-table-column>
<el-table-column label="菊花码缩率图" width="120" min-width="150">
<template slot-scope="scope">
<el-image :src="scope.row.qr" :preview-src-list="[scope.row.qr]">
</el-image>
</template>
</el-table-column>
<el-table-column prop="describe" label="站点描述" min-width="80" show-overflow-tooltip>
</el-table-column>
<el-table-column align="center" label="已绑航线" width="200">
<template slot-scope="scope">
<el-tag class="iconfont"
:class="scope.row.bind_route === null || scope.row.bind_route === '' ? 'icon-ic_tingyong' : 'icon-feihangluxian'"
:type="scope.row.bind_route === null || scope.row.bind_route === '' ? 'danger' : ''">
<font class="m-l-5">{{ scope.row.bind_route === null || scope.row.bind_route === '' ? "未绑定" :
scope.row.bind_route }}</font>
</el-tag>
</template>
</el-table-column>
<el-table-column prop="controler" label="操作" width="140" min-width="140">
<template slot-scope="scope">
<el-button-group>
<el-button type="danger" icon="el-icon-delete" @click="deleteSite([scope.row.id])">删除</el-button>
</el-button-group>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination class="m-t-20" layout="prev, pager, next" :current-page.sync="currentPage" :page-size="pageSize"
:total="siteListArr.length">
</el-pagination>
<div>
<video
ref="video"
autoplay
controls
playsinline
muted
width="640"
height="360"
></video>
</div>
</template>
<script>
import { countSelIdArr } from '@/utils'
import SelectionShopId from '@/components/SelectionShopId'
export default {
name: 'Site',
data () {
return {
pageSize: 8, //
currentPage: 1, //
form: {
shop_id: ''
}
url: 'http://82.156.122.87:80/rtc/v1/whep/?app=live&stream=livestream2',
sdk: null
}
},
components: {
SelectionShopId
},
computed: {
/**
* @description: 获取站点列表
*/
siteList () {
return this.$store.state.siteList
},
/**
* @description: 过滤掉 不对应客户的 站点列表
* @return: 站点列表
*/
siteListArr () {
if (this.form.shop_id !== '') {
return this.siteList.filter((item) => item.shop_id === this.form.shop_id)
} else {
return this.siteList
}
}
mounted () {
this.startPlay()
},
methods: {
countSelIdArr,
/**
* @description: 跳转到编辑页面
*/
toEditPage () {
const selId = this.countSelIdArr(this.$refs.myTable.selection)
switch (selId.length) {
case 0:
this.$message.error('请选择一条需要编辑的记录')
break
case 1:
this.$router.push('/site/edit/' + selId['0'])
break
default:
this.$message.error('只能选择一条记录')
async startPlay () {
if (this.sdk) {
this.sdk.close()
this.sdk = null
}
this.sdk = new window.SrsRtcWhipWhepAsync()
this.$refs.video.srcObject = this.sdk.stream
try {
const session = await this.sdk.play(this.url)
console.log('播放成功session:', session)
} catch (e) {
console.error('播放失败', e)
}
},
/**
* @description: 删除站点
*/
deleteSite (idArr) {
this.$store.dispatch('fetchDelSite', idArr)
}
},
watch: {
},
created () {
},
filters: {
countSelIdArr
}
}
</script>
<style lang="scss" scoped>
@import "@/styles/theme.scss";
.el-tag {
i {
vertical-align: middle
}
}
.image-placeholder {
position: relative;
display: inline-block;
}
</style>