feat: srs视频播放添加
This commit is contained in:
parent
76598d96c0
commit
e83fcfc461
@ -1,6 +1,16 @@
|
||||
import http from "@/api";
|
||||
const BASEURL = import.meta.env.VITE_API_URL;
|
||||
|
||||
// 查询人员信息列表
|
||||
export const getMemberInfoList = (params: {}) => {
|
||||
return http.post(BASEURL + `/xmgl/workerInfo/selectWorkerInfoList`, params);
|
||||
};
|
||||
|
||||
//查询所属企业
|
||||
export const getCompanyDataList = (params: {}) => {
|
||||
return http.post(BASEURL + `/xmgl/enterpriseInfo/list`, params);
|
||||
};
|
||||
|
||||
//查询人员人数
|
||||
export const getPersonTypeAndEduStatisticsApi = (params: {}) => {
|
||||
return http.post(BASEURL + `/xmgl/workerInfo/selectPersonTypeAndEduStatistics`, params);
|
||||
|
||||
707
src/assets/js/srs.sdk.js
Normal file
707
src/assets/js/srs.sdk.js
Normal file
@ -0,0 +1,707 @@
|
||||
//
|
||||
// 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(", ");
|
||||
}
|
||||
|
||||
export default {
|
||||
SrsRtcPublisherAsync,
|
||||
SrsRtcPlayerAsync,
|
||||
SrsRtcWhipWhepAsync,
|
||||
SrsRtcFormatSenders,
|
||||
SrsError
|
||||
};
|
||||
@ -37,6 +37,11 @@ export const staticRouter: RouteRecordRaw[] = [
|
||||
name: "视频管理",
|
||||
component: () => import("@/views/sevenLargeScreen/videoManagement/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/videoSrsManagement",
|
||||
name: "视频管理srs",
|
||||
component: () => import("@/views/sevenLargeScreen/videoSrsManagement/index.vue")
|
||||
},
|
||||
{
|
||||
path: "/distributionMonitoring",
|
||||
name: "配电箱监测",
|
||||
|
||||
@ -4,49 +4,52 @@
|
||||
<div class="top-search">
|
||||
<div class="search-item">
|
||||
<span>人员类型</span>
|
||||
<el-select class="m-2" placeholder="请选择" size="small" v-model="searchForm.alarmType"
|
||||
:clearable="true" style="width: 150px;">
|
||||
<el-option
|
||||
v-for="(item, index) in alarmTypeList"
|
||||
:key="index"
|
||||
:label="item"
|
||||
:value="index"
|
||||
/>
|
||||
<el-select
|
||||
class="m-2"
|
||||
placeholder="请选择"
|
||||
size="small"
|
||||
v-model="searchForm.memberType"
|
||||
:clearable="true"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option v-for="(item, index) in memberTypeList" :key="index" :label="item.name" :value="item.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<span>所属企业</span>
|
||||
<el-select class="m-2" placeholder="请选择" size="small" v-model="searchForm.name"
|
||||
:clearable="true" style="width: 150px;">
|
||||
<el-option
|
||||
v-for="(item, index) in deviceList"
|
||||
:key="index"
|
||||
:label="item.deviceName"
|
||||
:value="item.deviceId"
|
||||
/>
|
||||
<el-select
|
||||
class="m-2"
|
||||
placeholder="请选择"
|
||||
size="small"
|
||||
v-model="searchForm.belongCompany"
|
||||
:clearable="true"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option v-for="(item, index) in enterpriseListData" :key="index" :label="item.enterpriseName" :value="item.id" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<span>在职状态</span>
|
||||
<el-select class="m-2" placeholder="请选择" size="small" v-model="searchForm.name"
|
||||
:clearable="true" style="width: 150px;">
|
||||
<el-option
|
||||
v-for="(item, index) in deviceList"
|
||||
:key="index"
|
||||
:label="item.deviceName"
|
||||
:value="item.deviceId"
|
||||
/>
|
||||
<el-select
|
||||
class="m-2"
|
||||
placeholder="请选择"
|
||||
size="small"
|
||||
v-model="searchForm.workState"
|
||||
:clearable="true"
|
||||
style="width: 150px"
|
||||
>
|
||||
<el-option v-for="(item, index) in onlineWorkList" :key="index" :label="item.name" :value="item.value" />
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<span>姓名</span>
|
||||
<el-input placeholder="请输入" v-model="searchForm.name" style="width: 150px;"/>
|
||||
<el-input placeholder="请输入" v-model="searchForm.name" style="width: 150px" />
|
||||
</div>
|
||||
<div class="search-item">
|
||||
<span>身份证号</span>
|
||||
<el-input placeholder="请输入" v-model="searchForm.name" style="width: 180px;"/>
|
||||
<el-input placeholder="请输入" v-model="searchForm.idCard" style="width: 180px" />
|
||||
</div>
|
||||
<el-button @click="getHistoryAlarmList('search')">查询</el-button>
|
||||
<el-button @click="getMemberCountList('search')">查询</el-button>
|
||||
</div>
|
||||
<div class="tabList">
|
||||
<div>报警类型</div>
|
||||
@ -60,13 +63,13 @@
|
||||
|
||||
<el-scrollbar class="listBox" ref="refScrollbar">
|
||||
<div v-for="(item, index) in partyMemberList" class="listStyle" :key="item.id">
|
||||
<div>{{alarmTypeList[item.type]}}</div>
|
||||
<div>{{item.alarmTypeName}}</div>
|
||||
<div>{{item.deviceName}}</div>
|
||||
<div>{{item.avgData}}</div>
|
||||
<div>{{item.alarmValue}}</div>
|
||||
<div>{{item.tempAlarmTime}}</div>
|
||||
<div>{{item.exceedVal}}</div>
|
||||
<div>{{ alarmTypeList[item.type] }}</div>
|
||||
<div>{{ item.alarmTypeName }}</div>
|
||||
<div>{{ item.deviceName }}</div>
|
||||
<div>{{ item.avgData }}</div>
|
||||
<div>{{ item.alarmValue }}</div>
|
||||
<div>{{ item.tempAlarmTime }}</div>
|
||||
<div>{{ item.exceedVal }}</div>
|
||||
</div>
|
||||
<div class="notoDta" v-if="partyMemberList.length == 0">
|
||||
<img src="@/assets/images/noData.png" alt="" />
|
||||
@ -80,46 +83,54 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { GlobalStore } from "@/stores";
|
||||
import { environmentDevList, getAlarmCountTotalList } from "@/api/modules/headNoise";
|
||||
import { getCompanyDataList, getMemberInfoList } from "@/api/modules/labor";
|
||||
const store = GlobalStore();
|
||||
const props = defineProps(["tip"])
|
||||
const alarmTypeList = ref(['报警', '预警'])
|
||||
const props = defineProps(["tip"]);
|
||||
const onlineWorkList = ref([
|
||||
{ name: "在职", value: 1 },
|
||||
{ name: "离职", value: 2 }
|
||||
])
|
||||
const enterpriseListData = ref([] as any);
|
||||
const memberTypeList = ref([
|
||||
{ name: "劳务人员", value: 1 },
|
||||
{ name: "管理人员", value: 2 },
|
||||
{ name: "临时人员", value: 3 }
|
||||
]);
|
||||
const alarmTypeList = ref(["报警", "预警"]);
|
||||
let pageNo = ref(1 as any);
|
||||
let moreScroll = ref(true as any);
|
||||
const refScrollbar = ref(null as any); // 绑定到滚动的盒子上
|
||||
const deviceList = ref([] as any); // 设备列表
|
||||
const searchForm = ref({
|
||||
alarmType: '',
|
||||
name: "",
|
||||
rangeTime: ""
|
||||
})
|
||||
memberType: '',
|
||||
belongCompany: '',
|
||||
workState: '',
|
||||
name: '',
|
||||
idCard: ''
|
||||
});
|
||||
|
||||
const partyMemberList = ref({} as any);
|
||||
//获取设备下拉
|
||||
const getDevList = async () => {
|
||||
const res:any = await environmentDevList({ projectSn: store.sn });
|
||||
console.log("获取设备下拉", res);
|
||||
if (res.result.length > 0) {
|
||||
deviceList.value = res.result;
|
||||
searchForm.value.name = res.result[0].deviceId
|
||||
//获取企业列表
|
||||
const getCompanyList = async () => {
|
||||
let data = {
|
||||
projectSn: store.sn,
|
||||
enterpriseName: "",
|
||||
userEnterpriseId: store.userInfo.userEnterpriseId
|
||||
};
|
||||
const res: any = await getCompanyDataList(data);
|
||||
if (res.code == 200) {
|
||||
enterpriseListData.value = res.result;
|
||||
}
|
||||
};
|
||||
//获取历史报警数据
|
||||
const getHistoryAlarmList = async (tip:any) => {
|
||||
//获取数据
|
||||
const getMemberCountList = async (tip:any) => {
|
||||
let requestData:any = {
|
||||
deviceId: searchForm.value.name,
|
||||
projectSn: store.sn,
|
||||
alarmTypeId: props.tip,
|
||||
type: searchForm.value.alarmType,
|
||||
pageNo: tip == 'search'?1:pageNo.value,
|
||||
pageSize: 100
|
||||
}
|
||||
if(searchForm.value.rangeTime){
|
||||
requestData.startTime = searchForm.value.rangeTime[0];
|
||||
requestData.endTime = searchForm.value.rangeTime[1];
|
||||
}
|
||||
const res: any = await getAlarmCountTotalList(requestData);
|
||||
console.log("获取历史数据", res);
|
||||
const res: any = await getMemberInfoList(requestData);
|
||||
console.log("获取人员信息列表", res);
|
||||
if(tip == 'more'){
|
||||
partyMemberList.value = partyMemberList.value.concat(res.result.records);
|
||||
} else {
|
||||
@ -133,8 +144,8 @@ const getHistoryAlarmList = async (tip:any) => {
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
await getDevList();
|
||||
await getHistoryAlarmList('search');
|
||||
await getCompanyList();
|
||||
// await getHistoryAlarmList('search');
|
||||
refScrollbar.value.wrapRef.addEventListener("scroll", (e: any) => {
|
||||
// console.log("滚动容器", e);
|
||||
const scrollTop = e.target.scrollTop;
|
||||
@ -145,7 +156,7 @@ onMounted(async () => {
|
||||
if (scrollTop >= scrollHeight - clientHeight - 1) {
|
||||
// console.log("加载更多");
|
||||
if (moreScroll.value) {
|
||||
getHistoryAlarmList('more');
|
||||
getMemberCountList("more");
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -153,7 +164,7 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin flex{
|
||||
@mixin flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
@ -166,14 +177,14 @@ onMounted(async () => {
|
||||
margin-top: 10px;
|
||||
// background: url("@/assets/images/cardImg.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
.top-search{
|
||||
.top-search {
|
||||
@include flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 15px;
|
||||
.search-item{
|
||||
.search-item {
|
||||
@include flex;
|
||||
margin-right: 20px;
|
||||
span{
|
||||
span {
|
||||
color: white;
|
||||
margin-right: 10px;
|
||||
}
|
||||
@ -249,21 +260,22 @@ onMounted(async () => {
|
||||
}
|
||||
}
|
||||
// element 组件样式
|
||||
:deep(){
|
||||
.el-date-editor .el-range-input,.el-range-separator{
|
||||
color: #fff;
|
||||
}
|
||||
.el-input__wrapper {
|
||||
background: #112d59;
|
||||
}
|
||||
.el-input__inner {
|
||||
color: #fff;
|
||||
}
|
||||
.el-button{
|
||||
background-color: #2758C0;
|
||||
color: white;
|
||||
border-color: transparent;
|
||||
}
|
||||
:deep() {
|
||||
.el-date-editor .el-range-input,
|
||||
.el-range-separator {
|
||||
color: #fff;
|
||||
}
|
||||
.el-input__wrapper {
|
||||
background: #112d59;
|
||||
}
|
||||
.el-input__inner {
|
||||
color: #fff;
|
||||
}
|
||||
.el-button {
|
||||
background-color: #2758c0;
|
||||
color: white;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
// ::v-deep .el-select .el-input .el-select__caret {
|
||||
// color: #fff;
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
<!--ckplayer 视频播放-->
|
||||
<template>
|
||||
<div style="height: 100%">
|
||||
<video :id="'videoItem' + name" controls autoplay style="width: 100%; height: 100%; object-fit: fill" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Srs from "@/assets/js/srs.sdk";
|
||||
export default {
|
||||
props: ["videoUrls", "autoPlay", "poster", "deviceIp", "name"],
|
||||
data() {
|
||||
return {
|
||||
player: "",
|
||||
rtcPlayer: null
|
||||
};
|
||||
},
|
||||
mounted: function () {
|
||||
console.log(this.videoUrls, this.deviceIp, this.autoPlay, this.name, 666);
|
||||
this.player = document.getElementById("videoItem" + this.name);
|
||||
//方法一:使用srs.sdk.js
|
||||
this.rtcPlayer = new Srs.SrsRtcWhipWhepAsync();
|
||||
this.rtcPlayer.play(this.videoUrls);
|
||||
// video标签
|
||||
this.player.srcObject = this.rtcPlayer.stream;
|
||||
// 方法二:使用jswebrtc.min.js
|
||||
// new JSWebrtc.Player(this.url, { video: player, autoplay: true, });
|
||||
},
|
||||
beforeDestroy: function () {
|
||||
this.rtcPlayer.close();
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
527
src/views/sevenLargeScreen/videoSrsManagement/index.vue
Normal file
527
src/views/sevenLargeScreen/videoSrsManagement/index.vue
Normal file
@ -0,0 +1,527 @@
|
||||
<template>
|
||||
<div class="videoManage">
|
||||
<div class="left">
|
||||
<div class="videoListBig">
|
||||
<Card title="监控设备列表">
|
||||
<el-scrollbar style="height: 100%">
|
||||
<div class="decivList">
|
||||
<div
|
||||
class="menuDev"
|
||||
v-for="item in shipinList"
|
||||
:key="item.id"
|
||||
:class="item.deviceState == 1 ? 'online' : 'offline'"
|
||||
@click="checkVideo(item)"
|
||||
>
|
||||
<div class="decName">
|
||||
<span style="white-space: nowrap"> {{ item.videoName }}</span>
|
||||
</div>
|
||||
<div class="status">{{ item.deviceState == 1 ? "在线" : "离线" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<div class="notoDta" v-if="shipinList.length == 0">
|
||||
<img src="@/assets/images/noData.png" alt="" />
|
||||
<p>暂无数据</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="videoPlayerBig">
|
||||
<Card title="视频监控">
|
||||
<div class="select-right" @click="goToSafeHelmet" v-if="COMPANY === 'syhy'">
|
||||
<div class="safe-helmet">智能安全帽</div>
|
||||
</div>
|
||||
<!-- 萤石云播放 -->
|
||||
<div ref="playWndBox" style="width: 100%; height: 100%; margin: 0 5% 2% 5%" v-if="videoType === 1">
|
||||
<ysyPlayAndPlayback :ref="'ysy'" :ysyParams="ysyParams"></ysyPlayAndPlayback>
|
||||
</div>
|
||||
<!-- 内网播放(播放器) -->
|
||||
<div class="Content">
|
||||
<div class="flex-col page space-y-16">
|
||||
<div class="flex-col fullContent">
|
||||
<div class="flex-row justify-between group">
|
||||
<div class="flex-row items-center space-x-16"></div>
|
||||
|
||||
<div class="flex-row items-center space-x-16">
|
||||
<img v-show="showType == 1" class="shrink-0 image_3 cursorPointer" src="@/assets/images/videoOne1.png" />
|
||||
<img
|
||||
v-show="showType != 1"
|
||||
@click="changeShowType(1)"
|
||||
class="shrink-0 image_3 cursorPointer"
|
||||
src="@/assets/images/videoOne.png"
|
||||
/>
|
||||
<img v-show="showType == 4" class="shrink-0 image_4" src="@/assets/images/videoTwo1.png" />
|
||||
<img
|
||||
v-show="showType != 4"
|
||||
@click="changeShowType(4)"
|
||||
class="shrink-0 image_4 cursorPointer"
|
||||
src="@/assets/images/videoTwo.png"
|
||||
/>
|
||||
<img v-show="showType == 9" class="shrink-0 image_4" src="@/assets/images/videoThree1.png" />
|
||||
<img
|
||||
v-show="showType != 9"
|
||||
@click="changeShowType(9)"
|
||||
class="shrink-0 image_4 cursorPointer"
|
||||
src="@/assets/images/videoThree.png"
|
||||
/>
|
||||
<img v-show="showType == 12" class="shrink-0 image_4" src="@/assets/images/videoFour1.png" />
|
||||
<img
|
||||
v-show="showType != 12"
|
||||
@click="changeShowType(12)"
|
||||
class="shrink-0 image_4 cursorPointer"
|
||||
src="@/assets/images/videoFour.png"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row items-start relative group_2 space-x-16">
|
||||
<div class="flex-row justify-start items-start relative group_3" :class="'showType' + showType">
|
||||
<div
|
||||
class="section_9 relative"
|
||||
v-for="(item, index) in videoList"
|
||||
:key="item.itemId"
|
||||
@click="selectFn(index)"
|
||||
:class="index == winIndex ? 'active' : ''"
|
||||
v-show="index < showType"
|
||||
>
|
||||
<div class="flex-col section_3 space-y-7">
|
||||
<div class="flex-row justify-between">
|
||||
<div class="flex-row space-x-12 nameBox">
|
||||
<span class="font_1 desc">设备名称</span>
|
||||
<span class="font_1 name">{{ item.name }}</span>
|
||||
</div>
|
||||
<div class="closeBtn" @click="deletePlayer(index)">×</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-col section_10">
|
||||
<ckplayerComp
|
||||
:name="index"
|
||||
:poster="''"
|
||||
:deviceIp="`http://${item.account}:${item.password}`"
|
||||
:videoUrls="item.serialNumber"
|
||||
:autoPlay="true"
|
||||
></ckplayerComp>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Card from "@/components/card.vue";
|
||||
import ysyPlayAndPlayback from "@/components/ysyPlayAndPlayback.vue";
|
||||
import ckplayerComp from "./ckplayerComp.vue";
|
||||
import { ref, onMounted } from "vue";
|
||||
import { GlobalStore } from "@/stores";
|
||||
import { selectProjectVideoListApi, getSafeHatSessionApi } from "@/api/modules/video";
|
||||
import { COMPANY } from "@/config/config";
|
||||
const winIndex = ref(0);
|
||||
const videoList = ref([] as any);
|
||||
const showType = ref(1);
|
||||
let videoType = ref("") as any;
|
||||
let ysyParams = ref({} as any);
|
||||
|
||||
let shipinList = ref([] as any);
|
||||
const store = GlobalStore();
|
||||
const playWndBox = ref(null);
|
||||
let cameraIndexCode = ref<Array<string>>([]);
|
||||
const deletePlayer = (index: any) => {
|
||||
videoList.value.splice(index, 1);
|
||||
};
|
||||
const selectFn = (index: any) => {
|
||||
winIndex.value = index;
|
||||
};
|
||||
const changeShowType = (type: any) => {
|
||||
showType.value = type;
|
||||
};
|
||||
const goToSafeHelmet = async () => {
|
||||
const res: any = await getSafeHatSessionApi({ projectSn: store.sn });
|
||||
if (res.result) {
|
||||
console.log(res, "安全帽");
|
||||
let url = "https://caps.runde.pro/login#token=" + res.result.token + "&user_name=" + res.result.userName + "&target=home";
|
||||
// let url = 'https://caps.runde.pro/login#token='+ res.result.token + '&user_name=' + res.result.userName + '&target=home'
|
||||
window.open(url);
|
||||
}
|
||||
};
|
||||
|
||||
//设备列表的点击操作
|
||||
const checkVideo = async (item: any) => {
|
||||
ysyParams.value = item;
|
||||
let itemVal = null;
|
||||
itemVal = videoList.value.find((item2: any) => item.itemId == item2.itemId);
|
||||
console.log(itemVal);
|
||||
if (showType.value == 1) {
|
||||
videoList.value = [item];
|
||||
}
|
||||
if (!itemVal && showType.value != 1) {
|
||||
videoList.value.push(item);
|
||||
}
|
||||
console.log("右边的值", videoList.value);
|
||||
};
|
||||
const getVideoList = async () => {
|
||||
let res: any = await selectProjectVideoListApi({
|
||||
projectSn: store.sn,
|
||||
all: 1
|
||||
// all=1查全部
|
||||
});
|
||||
shipinList.value = res.result.videoList;
|
||||
videoList.value = res.result.videoList.concat(); // 产生一个新数组
|
||||
console.log("视频的列表", res.result);
|
||||
};
|
||||
onMounted(async () => {
|
||||
await getVideoList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.videoManage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
.left {
|
||||
width: 17%;
|
||||
height: 100%;
|
||||
.videoListBig {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.decivList {
|
||||
width: 100%;
|
||||
height: 70%;
|
||||
.menuDev {
|
||||
height: 6%;
|
||||
background: url("@/assets/images/dustNoise/listImg.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
line-height: 35px;
|
||||
font-size: calc(100vw * 14 / 1920);
|
||||
margin: 2% 3%;
|
||||
cursor: pointer;
|
||||
.decName {
|
||||
width: 30%;
|
||||
span {
|
||||
margin-left: 10%;
|
||||
}
|
||||
}
|
||||
.status {
|
||||
margin-left: 56%;
|
||||
}
|
||||
}
|
||||
.menuDev:hover {
|
||||
height: 6%;
|
||||
background: url("@/assets/images/dustNoise/devImg.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
.online {
|
||||
.status {
|
||||
color: #65d7f9;
|
||||
}
|
||||
}
|
||||
.offline {
|
||||
.status {
|
||||
color: #ec6266;
|
||||
}
|
||||
}
|
||||
}
|
||||
.right {
|
||||
margin-left: 1%;
|
||||
width: 83%;
|
||||
height: 100%;
|
||||
|
||||
.videoPlayerBig {
|
||||
width: 99%;
|
||||
height: 92%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
.notoDta {
|
||||
top: 50%;
|
||||
width: 12%;
|
||||
left: 2%;
|
||||
position: absolute;
|
||||
img {
|
||||
width: 40%;
|
||||
margin: 5% 30%;
|
||||
}
|
||||
p {
|
||||
color: #fff;
|
||||
font-size: calc(100vw * 14 / 1920);
|
||||
margin: -6% 37%;
|
||||
}
|
||||
}
|
||||
}
|
||||
::v-deep .h-card .content {
|
||||
margin-top: 1%;
|
||||
height: 100%;
|
||||
background: none;
|
||||
}
|
||||
::v-deep .h-card .title .titltText {
|
||||
margin-bottom: 0%;
|
||||
}
|
||||
|
||||
.select-right {
|
||||
width: 20%;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
right: -15%;
|
||||
top: 1%;
|
||||
.safe-helmet {
|
||||
padding: 0 6%;
|
||||
z-index: 99;
|
||||
cursor: pointer;
|
||||
background: url("@/assets/images/dustNoise/rightImg.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
}
|
||||
.Content {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
// background-color: #00091a;
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
.cursorPointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.fullContent {
|
||||
height: 100%;
|
||||
}
|
||||
.page {
|
||||
padding-top: 16px;
|
||||
// background-color: #05051a;
|
||||
// width: 100%;
|
||||
// overflow-y: auto;
|
||||
// overflow-x: hidden;
|
||||
height: calc(100% - 16px);
|
||||
|
||||
.image {
|
||||
margin-left: 24px;
|
||||
width: 162px;
|
||||
height: 24px;
|
||||
}
|
||||
.group {
|
||||
padding: 0 56px 0 30px;
|
||||
|
||||
// .section {
|
||||
// background-color: #0071f21a;
|
||||
// height: 34px;
|
||||
// border: solid 1px #0071f2;
|
||||
// .text {
|
||||
// color: #ffffffcc;
|
||||
// }
|
||||
// .image_2 {
|
||||
// width: 40px;
|
||||
// height: 32px;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
.group_2 {
|
||||
height: calc(100% - 26px);
|
||||
.group_3 {
|
||||
margin: 16px 0px;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
height: calc(100% - 32px);
|
||||
.section_9 {
|
||||
height: calc(100% - 10px);
|
||||
width: calc(100% - 11px);
|
||||
margin: 15px;
|
||||
// &:nth-child(2n) {
|
||||
// margin-right: 0;
|
||||
// }
|
||||
&.active {
|
||||
border: 1px solid #064599;
|
||||
}
|
||||
.section_10 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(14, 81, 156, 0.2);
|
||||
border: 1px solid rgba(12, 39, 74, 1);
|
||||
}
|
||||
}
|
||||
&.showType4 .section_9 {
|
||||
width: calc(50% - 32px);
|
||||
height: calc(50% - 25px);
|
||||
margin: 15px;
|
||||
// &:nth-child(3n) {
|
||||
// margin-right: 0;
|
||||
// }
|
||||
}
|
||||
&.showType9 .section_9 {
|
||||
width: 31%;
|
||||
height: 30%;
|
||||
margin: 15px;
|
||||
// &:nth-child(4n) {
|
||||
// margin-right: 0;
|
||||
// }
|
||||
}
|
||||
&.showType12 .section_9 {
|
||||
width: 23%;
|
||||
height: 30%;
|
||||
margin: 15px;
|
||||
// &:nth-child(4n) {
|
||||
// margin-right: 0;
|
||||
// }
|
||||
}
|
||||
.space-y-7 {
|
||||
& > *:not(:first-child) {
|
||||
margin-top: 7px;
|
||||
}
|
||||
}
|
||||
.space-x-12 {
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
.space-x-4 {
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.section_6 {
|
||||
background-color: #00ae4c;
|
||||
border-radius: 50%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
.font_2 {
|
||||
font-size: 12px;
|
||||
font-family: "PingFang SC";
|
||||
line-height: 17px;
|
||||
color: #ffffff;
|
||||
}
|
||||
.section_12 {
|
||||
background-color: #ab353a;
|
||||
border-radius: 50%;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
}
|
||||
.font_3 {
|
||||
font-size: 12px;
|
||||
font-family: "PingFang SC";
|
||||
line-height: 17px;
|
||||
color: #ffffffcc;
|
||||
}
|
||||
.section_3 {
|
||||
padding: 11px 12px;
|
||||
background-image: linear-gradient(-90deg, #174c9c00 0%, #174c9ce6 100%);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: calc(100% - 24px);
|
||||
z-index: 3;
|
||||
.nameBox {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
}
|
||||
.desc {
|
||||
width: 55px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.name {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.space-x-16 {
|
||||
& > *:not(:first-child) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.image_3 {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
.image_4 {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
}
|
||||
}
|
||||
.font_1 {
|
||||
font-size: 14px;
|
||||
font-family: "PingFang SC";
|
||||
line-height: 20px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
.space-y-16 {
|
||||
& > *:not(:first-child) {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.closeBtn {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
text-align: center;
|
||||
line-height: 12px;
|
||||
color: white;
|
||||
// border: 1px solid white;
|
||||
// border-radius: 50%;
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.flex-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.justify-evenly {
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
x
Reference in New Issue
Block a user