feat: 请求方法初始化,视频流写入demo完成
parent
8704bd645c
commit
a58eba6644
@ -0,0 +1,4 @@
|
||||
# .env.development
|
||||
NODE_ENV = development
|
||||
VITE_APP_ENV = development
|
||||
VITE_APP_BASE_API = http://192.168.10.14:8888
|
@ -0,0 +1,4 @@
|
||||
# .env.production
|
||||
NODE_ENV = production
|
||||
VITE_APP_ENV = production
|
||||
VITE_APP_BASE_API = https://api.production.com
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,381 @@
|
||||
var WebRtcStreamer = (function () {
|
||||
|
||||
/**
|
||||
* Interface with WebRTC-streamer API
|
||||
* @constructor
|
||||
* @param {string} videoElement - id of the video element tag
|
||||
* @param {string} srvurl - url of webrtc-streamer (default is current location)
|
||||
*/
|
||||
var WebRtcStreamer = function WebRtcStreamer(videoElement, srvurl) {
|
||||
if (typeof videoElement === "string") {
|
||||
this.videoElement = document.getElementById(videoElement);
|
||||
} else {
|
||||
this.videoElement = videoElement;
|
||||
}
|
||||
this.srvurl = srvurl || location.protocol + "//" + window.location.hostname + ":" + window.location.port;
|
||||
this.pc = null;
|
||||
|
||||
this.mediaConstraints = {
|
||||
offerToReceiveAudio: true,
|
||||
offerToReceiveVideo: true
|
||||
};
|
||||
|
||||
this.iceServers = null;
|
||||
this.earlyCandidates = [];
|
||||
}
|
||||
|
||||
WebRtcStreamer.prototype._handleHttpErrors = function (response) {
|
||||
if (!response.ok) {
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a WebRTC Stream to videoElement
|
||||
* @param {string} videourl - id of WebRTC video stream
|
||||
* @param {string} audiourl - id of WebRTC audio stream
|
||||
* @param {string} options - options of WebRTC call
|
||||
* @param {string} stream - local stream to send
|
||||
* @param {string} prefmime - prefered mime
|
||||
*/
|
||||
WebRtcStreamer.prototype.connect = function (videourl, audiourl, options, localstream, prefmime) {
|
||||
this.disconnect();
|
||||
|
||||
// getIceServers is not already received
|
||||
if (!this.iceServers) {
|
||||
console.log("Get IceServers");
|
||||
|
||||
fetch(this.srvurl + "/api/getIceServers")
|
||||
.then(this._handleHttpErrors)
|
||||
.then((response) => (response.json()))
|
||||
.then((response) => this.onReceiveGetIceServers(response, videourl, audiourl, options, localstream, prefmime))
|
||||
.catch((error) => this.onError("getIceServers " + error))
|
||||
|
||||
} else {
|
||||
this.onReceiveGetIceServers(this.iceServers, videourl, audiourl, options, localstream, prefmime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a WebRTC Stream and clear videoElement source
|
||||
*/
|
||||
WebRtcStreamer.prototype.disconnect = function () {
|
||||
if (this.videoElement?.srcObject) {
|
||||
this.videoElement.srcObject.getTracks().forEach(track => {
|
||||
track.stop()
|
||||
this.videoElement.srcObject.removeTrack(track);
|
||||
});
|
||||
}
|
||||
if (this.pc) {
|
||||
fetch(this.srvurl + "/api/hangup?peerid=" + this.pc.peerid)
|
||||
.then(this._handleHttpErrors)
|
||||
.catch((error) => this.onError("hangup " + error))
|
||||
|
||||
|
||||
try {
|
||||
this.pc.close();
|
||||
} catch (e) {
|
||||
console.log("Failure close peer connection:" + e);
|
||||
}
|
||||
this.pc = null;
|
||||
}
|
||||
}
|
||||
|
||||
WebRtcStreamer.prototype.filterPreferredCodec = function (sdp, prefmime) {
|
||||
const lines = sdp.split('\n');
|
||||
const [prefkind, prefcodec] = prefmime.toLowerCase().split('/');
|
||||
let currentMediaType = null;
|
||||
let sdpSections = [];
|
||||
let currentSection = [];
|
||||
|
||||
// Group lines into sections
|
||||
lines.forEach(line => {
|
||||
if (line.startsWith('m=')) {
|
||||
if (currentSection.length) {
|
||||
sdpSections.push(currentSection);
|
||||
}
|
||||
currentSection = [line];
|
||||
} else {
|
||||
currentSection.push(line);
|
||||
}
|
||||
});
|
||||
sdpSections.push(currentSection);
|
||||
|
||||
// Process each section
|
||||
const processedSections = sdpSections.map(section => {
|
||||
const firstLine = section[0];
|
||||
if (!firstLine.startsWith('m=' + prefkind)) {
|
||||
return section.join('\n');
|
||||
}
|
||||
|
||||
// Get payload types for preferred codec
|
||||
const rtpLines = section.filter(line => line.startsWith('a=rtpmap:'));
|
||||
const preferredPayloads = rtpLines
|
||||
.filter(line => line.toLowerCase().includes(prefcodec))
|
||||
.map(line => line.split(':')[1].split(' ')[0]);
|
||||
|
||||
if (preferredPayloads.length === 0) {
|
||||
return section.join('\n');
|
||||
}
|
||||
|
||||
// Modify m= line to only include preferred payloads
|
||||
const mLine = firstLine.split(' ');
|
||||
const newMLine = [...mLine.slice(0, 3), ...preferredPayloads].join(' ');
|
||||
|
||||
// Filter related attributes
|
||||
const filteredLines = section.filter(line => {
|
||||
if (line === firstLine) return false;
|
||||
if (line.startsWith('a=rtpmap:')) {
|
||||
return preferredPayloads.some(payload => line.startsWith(`a=rtpmap:${payload}`));
|
||||
}
|
||||
if (line.startsWith('a=fmtp:') || line.startsWith('a=rtcp-fb:')) {
|
||||
return preferredPayloads.some(payload => line.startsWith(`a=${line.split(':')[0].split('a=')[1]}:${payload}`));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return [newMLine, ...filteredLines].join('\n');
|
||||
});
|
||||
|
||||
return processedSections.join('\n');
|
||||
}
|
||||
|
||||
/*
|
||||
* GetIceServers callback
|
||||
*/
|
||||
WebRtcStreamer.prototype.onReceiveGetIceServers = function (iceServers, videourl, audiourl, options, stream, prefmime) {
|
||||
this.iceServers = iceServers;
|
||||
this.pcConfig = iceServers || {
|
||||
"iceServers": []
|
||||
};
|
||||
try {
|
||||
this.createPeerConnection();
|
||||
|
||||
let callurl = this.srvurl + "/api/call?peerid=" + this.pc.peerid + "&url=" + encodeURIComponent(videourl);
|
||||
if (audiourl) {
|
||||
callurl += "&audiourl=" + encodeURIComponent(audiourl);
|
||||
}
|
||||
if (options) {
|
||||
callurl += "&options=" + encodeURIComponent(options);
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
this.pc.addStream(stream);
|
||||
}
|
||||
|
||||
// clear early candidates
|
||||
this.earlyCandidates.length = 0;
|
||||
|
||||
// create Offer
|
||||
this.pc.createOffer(this.mediaConstraints).then((sessionDescription) => {
|
||||
console.log("Create offer:" + JSON.stringify(sessionDescription));
|
||||
|
||||
console.log(`video codecs:${Array.from(new Set(RTCRtpReceiver.getCapabilities("video")?.codecs?.map(codec => codec.mimeType)))}`)
|
||||
console.log(`audio codecs:${Array.from(new Set(RTCRtpReceiver.getCapabilities("audio")?.codecs?.map(codec => codec.mimeType)))}`)
|
||||
|
||||
if (prefmime != undefined) {
|
||||
//set prefered codec
|
||||
let [prefkind] = prefmime.split('/');
|
||||
if (prefkind != "video" && prefkind != "audio") {
|
||||
prefkind = "video";
|
||||
prefmime = prefkind + "/" + prefmime;
|
||||
}
|
||||
console.log("sdp:" + sessionDescription.sdp);
|
||||
sessionDescription.sdp = this.filterPreferredCodec(sessionDescription.sdp, prefmime);
|
||||
console.log("sdp:" + sessionDescription.sdp);
|
||||
}
|
||||
|
||||
|
||||
this.pc.setLocalDescription(sessionDescription)
|
||||
.then(() => {
|
||||
fetch(callurl, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(sessionDescription)
|
||||
})
|
||||
.then(this._handleHttpErrors)
|
||||
.then((response) => (response.json()))
|
||||
.catch((error) => this.onError("call " + error))
|
||||
.then((response) => this.onReceiveCall(response))
|
||||
.catch((error) => this.onError("call " + error))
|
||||
|
||||
}, (error) => {
|
||||
console.log("setLocalDescription error:" + JSON.stringify(error));
|
||||
});
|
||||
|
||||
}, (error) => {
|
||||
alert("Create offer error:" + JSON.stringify(error));
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
this.disconnect();
|
||||
alert("connect error: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
WebRtcStreamer.prototype.getIceCandidate = function () {
|
||||
fetch(this.srvurl + "/api/getIceCandidate?peerid=" + this.pc.peerid)
|
||||
.then(this._handleHttpErrors)
|
||||
.then((response) => (response.json()))
|
||||
.then((response) => this.onReceiveCandidate(response))
|
||||
.catch((error) => this.onError("getIceCandidate " + error))
|
||||
}
|
||||
|
||||
/*
|
||||
* create RTCPeerConnection
|
||||
*/
|
||||
WebRtcStreamer.prototype.createPeerConnection = function () {
|
||||
console.log("createPeerConnection config: " + JSON.stringify(this.pcConfig));
|
||||
this.pc = new RTCPeerConnection(this.pcConfig);
|
||||
let pc = this.pc;
|
||||
pc.peerid = Math.random();
|
||||
|
||||
pc.onicecandidate = (evt) => this.onIceCandidate(evt);
|
||||
pc.onaddstream = (evt) => this.onAddStream(evt);
|
||||
pc.oniceconnectionstatechange = (evt) => {
|
||||
console.log("oniceconnectionstatechange state: " + pc.iceConnectionState);
|
||||
if (this.videoElement) {
|
||||
if (pc.iceConnectionState === "connected") {
|
||||
this.videoElement.style.opacity = "1.0";
|
||||
} else if (pc.iceConnectionState === "disconnected") {
|
||||
this.videoElement.style.opacity = "0.25";
|
||||
} else if ((pc.iceConnectionState === "failed") || (pc.iceConnectionState === "closed")) {
|
||||
this.videoElement.style.opacity = "0.5";
|
||||
} else if (pc.iceConnectionState === "new") {
|
||||
this.getIceCandidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
pc.ondatachannel = function (evt) {
|
||||
console.log("remote datachannel created:" + JSON.stringify(evt));
|
||||
|
||||
evt.channel.onopen = function () {
|
||||
console.log("remote datachannel open");
|
||||
this.send("remote channel openned");
|
||||
}
|
||||
evt.channel.onmessage = function (event) {
|
||||
console.log("remote datachannel recv:" + JSON.stringify(event.data));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let dataChannel = pc.createDataChannel("ClientDataChannel");
|
||||
dataChannel.onopen = function () {
|
||||
console.log("local datachannel open");
|
||||
this.send("local channel openned");
|
||||
}
|
||||
dataChannel.onmessage = function (evt) {
|
||||
console.log("local datachannel recv:" + JSON.stringify(evt.data));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("Cannor create datachannel error: " + e);
|
||||
}
|
||||
|
||||
console.log("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig));
|
||||
return pc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RTCPeerConnection IceCandidate callback
|
||||
*/
|
||||
WebRtcStreamer.prototype.onIceCandidate = function (event) {
|
||||
if (event.candidate) {
|
||||
if (this.pc.currentRemoteDescription) {
|
||||
this.addIceCandidate(this.pc.peerid, event.candidate);
|
||||
} else {
|
||||
this.earlyCandidates.push(event.candidate);
|
||||
}
|
||||
} else {
|
||||
console.log("End of candidates.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
WebRtcStreamer.prototype.addIceCandidate = function (peerid, candidate) {
|
||||
fetch(this.srvurl + "/api/addIceCandidate?peerid=" + peerid, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(candidate)
|
||||
})
|
||||
.then(this._handleHttpErrors)
|
||||
.then((response) => (response.json()))
|
||||
.then((response) => {
|
||||
console.log("addIceCandidate ok:" + response)
|
||||
})
|
||||
.catch((error) => this.onError("addIceCandidate " + error))
|
||||
}
|
||||
|
||||
/*
|
||||
* RTCPeerConnection AddTrack callback
|
||||
*/
|
||||
WebRtcStreamer.prototype.onAddStream = function (event) {
|
||||
console.log("Remote track added:" + JSON.stringify(event));
|
||||
|
||||
this.videoElement.srcObject = event.stream;
|
||||
let promise = this.videoElement.play();
|
||||
if (promise !== undefined) {
|
||||
promise.catch((error) => {
|
||||
console.warn("error:" + error);
|
||||
this.videoElement.setAttribute("controls", true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* AJAX /call callback
|
||||
*/
|
||||
WebRtcStreamer.prototype.onReceiveCall = function (dataJson) {
|
||||
|
||||
console.log("offer: " + JSON.stringify(dataJson));
|
||||
let descr = new RTCSessionDescription(dataJson);
|
||||
this.pc.setRemoteDescription(descr).then(() => {
|
||||
console.log("setRemoteDescription ok");
|
||||
while (this.earlyCandidates.length) {
|
||||
let candidate = this.earlyCandidates.shift();
|
||||
this.addIceCandidate(this.pc.peerid, candidate);
|
||||
}
|
||||
|
||||
this.getIceCandidate()
|
||||
}, (error) => {
|
||||
console.log("setRemoteDescription error:" + JSON.stringify(error));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* AJAX /getIceCandidate callback
|
||||
*/
|
||||
WebRtcStreamer.prototype.onReceiveCandidate = function (dataJson) {
|
||||
console.log("candidate: " + JSON.stringify(dataJson));
|
||||
if (dataJson) {
|
||||
for (let i = 0; i < dataJson.length; i++) {
|
||||
let candidate = new RTCIceCandidate(dataJson[i]);
|
||||
|
||||
console.log("Adding ICE candidate :" + JSON.stringify(candidate));
|
||||
this.pc.addIceCandidate(candidate).then(() => {
|
||||
console.log("addIceCandidate OK");
|
||||
}, (error) => {
|
||||
console.log("addIceCandidate error:" + JSON.stringify(error));
|
||||
});
|
||||
}
|
||||
this.pc.addIceCandidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AJAX callback for Error
|
||||
*/
|
||||
WebRtcStreamer.prototype.onError = function (status) {
|
||||
console.log("onError:" + status);
|
||||
}
|
||||
|
||||
return WebRtcStreamer;
|
||||
})();
|
||||
|
||||
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
|
||||
window.WebRtcStreamer = WebRtcStreamer;
|
||||
}
|
||||
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = WebRtcStreamer;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* @Author: donghao donghao@supervision.ltd
|
||||
* @Date: 2025-03-12 15:13:38
|
||||
* @LastEditors: donghao donghao@supervision.ltd
|
||||
* @LastEditTime: 2025-03-12 19:20:43
|
||||
* @FilePath: \5G-Loading-Bay-Web\src\api\user.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import request from '@/utils/request/instance'
|
||||
import { config } from '@/config'
|
||||
|
||||
// 定义响应类型
|
||||
interface LoginRes {
|
||||
token: string
|
||||
userInfo: {
|
||||
id: number
|
||||
username: string
|
||||
}
|
||||
}
|
||||
|
||||
export const loginApi = (data: { username: string; password: string }) => {
|
||||
return request.post<LoginRes>(`${config.baseURL}/api/v1/user/login`, data, {
|
||||
showLoading: false // 单独关闭loading
|
||||
})
|
||||
}
|
||||
|
||||
// export const getUserInfo = (userId: number) => {
|
||||
// return request.get(`/user/info/${userId}`)
|
||||
// }
|
@ -1,4 +1,46 @@
|
||||
|
||||
<template>
|
||||
<!-- 实时视频播放器 -->
|
||||
<div class="video-player">
|
||||
<video ref="refPlayer" autoplay controls muted width="100%" height="100%" style="object-fit: fill;"></video>
|
||||
<!-- <iframe src="http://192.168.10.113:8889/cam/" frameborder="0"></iframe> -->
|
||||
</div>
|
||||
|
||||
<!-- http://192.168.10.113:8889/cam/ -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
const webRtcServer = ref(null);
|
||||
const refPlayer = ref(null);
|
||||
const videoSrc = 'rtsp://192.168.10.63:8554/mystream';
|
||||
|
||||
|
||||
function initData() {
|
||||
webRtcServer.value.connect(videoSrc || '', '', "rtptransport=tcp&timeout=60&width=320&height=0");
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 分别对应开发环境、生产环境视频流转码服务的地址
|
||||
// let srvUrl = 'http://127.0.0.1:8000';
|
||||
nextTick(() => {
|
||||
console.log(location.protocol, "refPlayer");
|
||||
// TODO 根据环境切换地址
|
||||
webRtcServer.value = new WebRtcStreamer(refPlayer.value, `http://192.168.10.26:9988`);
|
||||
nextTick(() => {
|
||||
videoSrc && initData();
|
||||
})
|
||||
})
|
||||
})
|
||||
onUnmounted(() => {
|
||||
webRtcServer.disconnect();
|
||||
webRtcServer.value = null;
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.video-player {
|
||||
position: fixed;
|
||||
width: 50%;
|
||||
z-index: 999999999;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,13 @@
|
||||
const env = import.meta.env
|
||||
|
||||
export const config = {
|
||||
env: env.VITE_APP_ENV,
|
||||
baseURL: env.VITE_APP_BASE_API,
|
||||
timeout: 10000,
|
||||
url: '',
|
||||
showLoading: false,
|
||||
loadingInstance: null,
|
||||
// uploadURL: env.VITE_APP_UPLOAD_URL,
|
||||
// appName: env.VITE_APP_NAME,
|
||||
// version: env.VITE_APP_VERSION
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_ENV: 'development' | 'staging' | 'production'
|
||||
readonly VITE_APP_BASE_API: string
|
||||
readonly VITE_APP_UPLOAD_URL: string
|
||||
readonly VITE_APP_NAME: string
|
||||
readonly VITE_APP_VERSION: string
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
/*
|
||||
* @Author: donghao donghao@supervision.ltd
|
||||
* @Date: 2025-03-12 18:59:29
|
||||
* @LastEditors: donghao donghao@supervision.ltd
|
||||
* @LastEditTime: 2025-03-12 18:59:36
|
||||
* @FilePath: \5G-Loading-Bay-Web\src\utils\request.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
// src/utils/request.ts
|
||||
const baseURL = import.meta.env.VITE_APP_BASE_API
|
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* @Author: donghao donghao@supervision.ltd
|
||||
* @Date: 2025-03-12 15:11:56
|
||||
* @LastEditors: donghao donghao@supervision.ltd
|
||||
* @LastEditTime: 2025-03-12 15:12:06
|
||||
* @FilePath: \5G-Loading-Bay-Web\src\utils\request\index.ts
|
||||
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
|
||||
*/
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { RequestConfig, RequestInterceptors } from './type'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
class Request {
|
||||
instance: AxiosInstance
|
||||
interceptors?: RequestInterceptors
|
||||
|
||||
constructor(config: RequestConfig) {
|
||||
this.instance = axios.create(config)
|
||||
this.interceptors = config.interceptors
|
||||
|
||||
// 全局请求拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
(config: RequestConfig) => {
|
||||
// 处理 token
|
||||
const userStore = useUserStore()
|
||||
if (userStore.token) {
|
||||
config.headers!.Authorization = `Bearer ${userStore.token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
(error: any) => Promise.reject(error)
|
||||
)
|
||||
|
||||
// 实例拦截器
|
||||
this.instance.interceptors.request.use(
|
||||
this.interceptors?.requestInterceptor,
|
||||
this.interceptors?.requestInterceptorCatch
|
||||
)
|
||||
|
||||
this.instance.interceptors.response.use(
|
||||
this.interceptors?.responseInterceptor,
|
||||
this.interceptors?.responseInterceptorCatch
|
||||
)
|
||||
|
||||
// 全局响应拦截器
|
||||
this.instance.interceptors.response.use(
|
||||
(res: AxiosResponse) => {
|
||||
const { code, message } = res.data
|
||||
if (code !== 200) {
|
||||
ElMessage.error(message || '请求失败')
|
||||
return Promise.reject(message)
|
||||
}
|
||||
return res.data
|
||||
},
|
||||
(error: any) => {
|
||||
// 处理 HTTP 状态码
|
||||
if (error.response?.status === 401) {
|
||||
ElMessageBox.confirm('登录已过期,请重新登录', '提示', {
|
||||
confirmButtonText: '重新登录',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const userStore = useUserStore()
|
||||
userStore.logout()
|
||||
location.reload()
|
||||
})
|
||||
}
|
||||
ElMessage.error(error.message || '请求错误')
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
request<T = any>(config: RequestConfig<T>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 单个请求的拦截器
|
||||
if (config.interceptors?.requestInterceptor) {
|
||||
config = config.interceptors.requestInterceptor(config)
|
||||
}
|
||||
|
||||
this.instance
|
||||
.request<any, T>(config)
|
||||
.then(res => {
|
||||
if (config.interceptors?.responseInterceptor) {
|
||||
res = config.interceptors.responseInterceptor(res)
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
.catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
get<T = any>(url: string, config?: RequestConfig<T>): Promise<T> {
|
||||
return this.request<T>({ ...config, method: 'GET', url })
|
||||
}
|
||||
|
||||
post<T = any>(url: string, data?: any, config?: RequestConfig<T>): Promise<T> {
|
||||
return this.request<T>({ ...config, method: 'POST', url, data })
|
||||
}
|
||||
|
||||
// 其他方法类似...
|
||||
}
|
||||
|
||||
export default Request
|
@ -0,0 +1,50 @@
|
||||
import Request from './index'
|
||||
import { config } from '@/config'
|
||||
import { ElLoading } from "element-plus";
|
||||
|
||||
// 根据环境显示不同提示
|
||||
const envTagMap = {
|
||||
development: '【开发环境】',
|
||||
// staging: '【测试环境】',
|
||||
production: '【生产环境】'
|
||||
}
|
||||
|
||||
const request = new Request({
|
||||
baseURL: config.baseURL,
|
||||
timeout: config.timeout,
|
||||
interceptors: {
|
||||
requestInterceptor: config => {
|
||||
// 开发环境显示环境标识
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`${envTagMap[config.env]} 请求路径: ${config.url}`)
|
||||
}
|
||||
|
||||
// 全局 loading 配置
|
||||
if (config.showLoading !== false) {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '加载中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
config.loadingInstance = loading
|
||||
}
|
||||
return config
|
||||
},
|
||||
|
||||
responseInterceptor: res => {
|
||||
// 关闭 loading
|
||||
res.config.loadingInstance?.close()
|
||||
|
||||
// 测试环境记录详细日志
|
||||
// if (config.env === 'staging') {
|
||||
// console.groupCollapsed(`[${res.config.method}] ${res.config.url}`)
|
||||
// console.log('请求参数:', res.config.data)
|
||||
// console.log('响应数据:', res.data)
|
||||
// console.groupEnd()
|
||||
// }
|
||||
return res
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default request
|
@ -0,0 +1,13 @@
|
||||
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
|
||||
export interface RequestInterceptors<T = AxiosResponse> {
|
||||
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
|
||||
requestInterceptorCatch?: (error: any) => any
|
||||
responseInterceptor?: (res: T) => T
|
||||
responseInterceptorCatch?: (error: any) => any
|
||||
}
|
||||
|
||||
export interface RequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
|
||||
interceptors?: RequestInterceptors<T>
|
||||
showLoading?: boolean
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
.pole-monitor-wrap {
|
||||
background-image: url("@/assets/common/bg_banner_1.png");
|
||||
background-size: cover;
|
||||
background-position: bottom;
|
||||
background-repeat: no-repeat;
|
||||
height: 823px;
|
||||
.search-section {
|
||||
padding: 16px 0;
|
||||
}
|
||||
.pole-main-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.pole-monitor-search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.right-panel {
|
||||
.el-scrollbar__view {
|
||||
background: transparent !important;
|
||||
height: 600px;
|
||||
}
|
||||
}
|
||||
.pole-monitor-main {
|
||||
.left-panel {
|
||||
.main-image {
|
||||
box-sizing: border-box;
|
||||
min-height: 511px;
|
||||
position: relative;
|
||||
background-color: #090F48;
|
||||
border-radius: 4px;
|
||||
img {
|
||||
width: 100%;
|
||||
max-height: 460px;
|
||||
}
|
||||
.image-info{
|
||||
position: absolute;
|
||||
height: 52px;
|
||||
line-height: 52px;
|
||||
bottom: 0;
|
||||
font-size: 14px;
|
||||
padding: 0 16px;
|
||||
&> span {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.thumbnail-container {
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
.swiper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.swiper-slide {
|
||||
width: 20%;
|
||||
border-radius: 4px;
|
||||
img {
|
||||
width: 100%;
|
||||
height: 144px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.active-slide img {
|
||||
border-radius: 4px;
|
||||
border: 2px solid #2ecce0;
|
||||
}
|
||||
.swiper-button-prev,
|
||||
.swiper-button-next {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.swiper-button-prev::after,
|
||||
.swiper-button-next::after {
|
||||
font-size: 12px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 修改按钮悬停样式 */
|
||||
.swiper-button-prev:hover,
|
||||
.swiper-button-next:hover {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div class="video-player">
|
||||
<video ref="refPlayer" autoplay controls muted width="100%" height="100%" style="object-fit: fill;"></video>
|
||||
<!-- <iframe src="http://192.168.10.113:8889/cam/" frameborder="0"></iframe> -->
|
||||
</div>
|
||||
|
||||
<!-- http://192.168.10.113:8889/cam/ -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick } from 'vue';
|
||||
|
||||
const webRtcServer = ref(null);
|
||||
const refPlayer = ref(null);
|
||||
const videoSrc = 'rtsp://192.168.10.63:8554/mystream';
|
||||
|
||||
|
||||
function initData() {
|
||||
webRtcServer.value.connect(videoSrc || '', '', "rtptransport=tcp&timeout=60&width=320&height=0");
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 分别对应开发环境、生产环境视频流转码服务的地址
|
||||
// let srvUrl = 'http://127.0.0.1:8000';
|
||||
nextTick(() => {
|
||||
console.log(location.protocol, "refPlayer");
|
||||
// TODO 根据环境切换地址
|
||||
webRtcServer.value = new WebRtcStreamer(refPlayer.value, `http://192.168.10.26:9988`);
|
||||
nextTick(() => {
|
||||
videoSrc && initData();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
onUnmounted(() => {
|
||||
webRtcServer.disconnect();
|
||||
webRtcServer.value = null;
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.video-player {
|
||||
position: fixed;
|
||||
width: 50%;
|
||||
z-index: 999999999;
|
||||
}
|
||||
</style>
|
@ -1 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
Loading…
Reference in New Issue