feat: srs播放器方式添加
This commit is contained in:
parent
a332db7a4b
commit
6439d82be5
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
|
||||
};
|
||||
@ -3034,6 +3034,13 @@ const routes2 = [
|
||||
component: (resolve) =>
|
||||
require(["@/views/projectFront/insideVideo/overview.vue"], resolve),
|
||||
},
|
||||
// 内网视频(srs)
|
||||
{
|
||||
path: "/project/insideSrsVideo",
|
||||
name: "insideSrsVideo",
|
||||
component: (resolve) =>
|
||||
require(["@/views/projectFront/insideSrsVideo/overview.vue"], resolve),
|
||||
},
|
||||
{
|
||||
path: "/project/imgRanging",
|
||||
name: "imgRanging",
|
||||
|
||||
34
src/views/projectFront/insideSrsVideo/ckplayerComp.vue
Normal file
34
src/views/projectFront/insideSrsVideo/ckplayerComp.vue
Normal file
@ -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>
|
||||
494
src/views/projectFront/insideSrsVideo/overview.vue
Normal file
494
src/views/projectFront/insideSrsVideo/overview.vue
Normal file
@ -0,0 +1,494 @@
|
||||
<template>
|
||||
<div class="fullHeight videoOverview">
|
||||
<closePage></closePage>
|
||||
<div class="header"></div>
|
||||
<div class="videoContent">
|
||||
<div class="videoListBox fullHeight">
|
||||
<div class="pageTitle">视频列表</div>
|
||||
<div class="videoBox" v-if="dataType == 1">
|
||||
<vue-scroll>
|
||||
<ul v-if="devList.length > 0">
|
||||
<li
|
||||
@click.stop="changeVideo(item)"
|
||||
v-for="(item, index) in devList"
|
||||
:key="index"
|
||||
>
|
||||
<img
|
||||
v-if="item.deviceType == 2"
|
||||
src="@/assets/images/icon-video2-white.png"
|
||||
/>
|
||||
<img v-else src="@/assets/images/icon-video-white.png" />
|
||||
{{ item.videoName }}
|
||||
</li>
|
||||
</ul>
|
||||
<div class="placeholderBox" v-else>
|
||||
<img src="@/assets/images/noData2-dark.png" alt="" srcset="" />
|
||||
</div>
|
||||
</vue-scroll>
|
||||
</div>
|
||||
<div class="videoBox" v-else>
|
||||
<vue-scroll>
|
||||
<el-collapse v-model="activeNames" style="padding: 0 20px">
|
||||
<el-collapse-item
|
||||
:title="item.name"
|
||||
:name="index"
|
||||
v-for="(item, index) in devList"
|
||||
:key="index"
|
||||
>
|
||||
<ul v-if="item.list.length > 0">
|
||||
<li
|
||||
@click.stop="changeVideo(data)"
|
||||
v-for="(data, index2) in item.list"
|
||||
:key="index2"
|
||||
>
|
||||
<img
|
||||
v-if="data.deviceType == 2"
|
||||
src="@/assets/images/icon-video2-white.png"
|
||||
/>
|
||||
<img v-else src="@/assets/images/icon-video-white.png" />
|
||||
{{ data.videoName }}
|
||||
</li>
|
||||
</ul>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</vue-scroll>
|
||||
</div>
|
||||
</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 == 8"
|
||||
class="shrink-0 image_4"
|
||||
src="@/assets/images/videoThree1.png"
|
||||
/>
|
||||
<img
|
||||
v-show="showType != 8"
|
||||
@click="changeShowType(8)"
|
||||
class="shrink-0 image_4 cursorPointer"
|
||||
src="@/assets/images/videoThree.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_8 relative"
|
||||
v-for="(item, index) in videoList"
|
||||
:key="item.itemId"
|
||||
@click="selectFn(index)"
|
||||
:class="index == winIndex ? 'active' : ''"
|
||||
>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import {
|
||||
selectProjectVideoListApi,
|
||||
selectUserVideoListApi,
|
||||
} from "@/assets/js/api/equipmentCenter/cameraList";
|
||||
import closePage from "@/components/closePage";
|
||||
import ckplayerComp from "./ckplayerComp";
|
||||
export default {
|
||||
components: { closePage, ckplayerComp },
|
||||
data() {
|
||||
return {
|
||||
winIndex: 0,
|
||||
videoList: [],
|
||||
showType: 1,
|
||||
devList: [],
|
||||
dataType: 1,
|
||||
activeNames: [0],
|
||||
screens: {},
|
||||
pluginType: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// console.dir(window.parent)
|
||||
// console.log(window.parent.document.getElementById("myFrame"), 12312313)
|
||||
// if (this.$store.state.userInfo.accountType != 6) {
|
||||
// this.loadData()
|
||||
// } else {
|
||||
// this.loadData2()
|
||||
// }
|
||||
this.loadData();
|
||||
},
|
||||
|
||||
methods: {
|
||||
deletePlayer(index) {
|
||||
this.videoList.splice(index, 1);
|
||||
},
|
||||
selectFn(index) {
|
||||
this.winIndex = index;
|
||||
},
|
||||
changeShowType(type) {
|
||||
this.showType = type;
|
||||
},
|
||||
changeVideo(item) {
|
||||
console.log("点击了列表----");
|
||||
console.log(item, "xxxxxxxxxx");
|
||||
let itemVal = null;
|
||||
itemVal = this.videoList.find((item2) => item.itemId == item2.itemId);
|
||||
console.log(itemVal);
|
||||
if (this.showType == 1) {
|
||||
this.videoList = [item];
|
||||
}
|
||||
if (!itemVal && this.showType != 1) {
|
||||
this.videoList.push(item);
|
||||
}
|
||||
console.log("右边的值", this.videoList);
|
||||
},
|
||||
loadData() {
|
||||
selectProjectVideoListApi({
|
||||
projectSn: this.$store.state.projectSn,
|
||||
}).then((res) => {
|
||||
var DATA = res.result.videoList;
|
||||
this.devList = DATA;
|
||||
this.videoList = this.devList.concat(); // 产生一个新数组
|
||||
console.log("视频的列表", res.result);
|
||||
this.dataType = res.result.type;
|
||||
});
|
||||
},
|
||||
loadData2() {
|
||||
selectUserVideoListApi({
|
||||
projectSn: this.$store.state.projectSn,
|
||||
userId: this.$store.state.userInfo.userId,
|
||||
}).then((res) => {
|
||||
var DATA = res.result.videoList;
|
||||
this.devList = DATA;
|
||||
this.videoList = this.devList;
|
||||
this.dataType = res.result.type;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@import "./style.less";
|
||||
.videoListBox {
|
||||
width: 300px;
|
||||
color: white;
|
||||
background-color: #01112d;
|
||||
}
|
||||
.pageTitle {
|
||||
margin: 12px 0;
|
||||
background-color: #01112d;
|
||||
&::before {
|
||||
background-color: #00fafa;
|
||||
}
|
||||
}
|
||||
.videoBox {
|
||||
background-color: #00091a;
|
||||
height: calc(100% - 30px);
|
||||
margin: 0 15px 0 0;
|
||||
position: relative;
|
||||
li {
|
||||
padding: 10px 25px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
margin-right: 8px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #003e4b;
|
||||
}
|
||||
}
|
||||
/deep/.el-collapse-item__header,
|
||||
/deep/.el-collapse-item__wrap {
|
||||
background-color: transparent;
|
||||
color: white;
|
||||
}
|
||||
/deep/.el-collapse-item__content {
|
||||
color: white;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
.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_8 {
|
||||
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_8 {
|
||||
width: calc(50% - 32px);
|
||||
height: calc(50% - 25px);
|
||||
margin: 15px;
|
||||
// &:nth-child(3n) {
|
||||
// margin-right: 0;
|
||||
// }
|
||||
}
|
||||
&.showType8 .section_8 {
|
||||
width: 31%;
|
||||
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>
|
||||
14
src/views/projectFront/insideSrsVideo/style.less
Normal file
14
src/views/projectFront/insideSrsVideo/style.less
Normal file
@ -0,0 +1,14 @@
|
||||
.videoOverview{
|
||||
background-color: #0D1534;
|
||||
}
|
||||
.header{
|
||||
background: url('../../../assets/images/overview3/headerBG.png') center;
|
||||
height: 70px;
|
||||
width: 100%;
|
||||
}
|
||||
.videoContent{
|
||||
display: flex;
|
||||
height: calc(100% - 70px);
|
||||
// background-color: #2FBBEC;
|
||||
overflow: hidden;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user