1.一个视频控制其他视频

2.四个视频放一起
3.采用zlmrtc
杭州-烟草
LAPTOP-S9HJSOEB\昊天 11 months ago
parent ab54e66297
commit c028f200aa

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -0,0 +1,240 @@
<template>
<div
id="app"
class="parent"
>
<video
style="width: 100%; height: 100%; object-fit: fill;"
ref="video"
muted
autoplay
>
</video>
<!-- <ptz v-if="ptzShow" :cameraId="cameraId" /> -->
</div>
</template>
<script>
import { ZLMRTCClient } from "../camera/ZLMediaKit-Webrtc-Vue/ZLMRTCClient";
export default {
props: {
cameraId: {
default: 2,
},
ptzShow: {
type: Boolean,
default: true,
},
},
data() {
return {
video: null,
selfVideo: null,
url: null,
simulcast: null,
useCamera: null,
audioEnable: null,
videoEnable: null,
datachannel: null,
msgsend: null,
msgrecv: "",
streamUrl: "",
player: null,
recvOnly: true,
resolutions: [],
resolution: "",
};
},
watch: {
cameraId(newCameraId, oldCameraId) {
console.log(newCameraId);
if (newCameraId !== oldCameraId) {
this.getHttp()
this.startVideo();
}
},
},
mounted() {
this.video = this.$refs.video;
this.getHttp()
ZLMRTCClient.GetAllScanResolution().forEach((r, i) => {
let text = r.label + "(" + r.width + "x" + r.height + ")";
this.resolutions.push({
text: text,
value: r,
});
});
this.resolution = this.resolutions[0].text;
this.startVideo();
},
beforeDestroy() {
this.stopVideo();
},
methods: {
getHttp(){
this.streamUrl =
"http://127.0.0.1:8096/index/api/webrtc?app=live&stream=camera" +
this.cameraId +
"&type=play";
},
radioChange(value) {
let urlObj = new URL(this.streamUrl);
urlObj.searchParams.set("type", value);
this.streamUrl = urlObj.href;
if (value == "play") {
this.recvOnly = true;
} else if (value == "echo") {
this.recvOnly = false;
} else {
this.recvOnly = false;
}
},
changeResolution(e) {
this.resolution = e.target.options[e.target.selectedIndex].text;
},
start_play() {
console.log(this.cameraId);
let res = this.resolution.match(/\d+/g);
let h = parseInt(res.pop());
let w = parseInt(res.pop());
this.player = new ZLMRTCClient.Endpoint({
element: this.video,
debug: false,
zlmsdpUrl: this.streamUrl,
simulecast: false,
useCamera: false,
audioEnable: false,
videoEnable: true,
recvOnly: this.recvOnly,
usedatachannel: false,
resolution: {
w: "100px",
h: "100px",
},
});
this.player.on(
ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR,
(e) => {
console.log("ICE 协商出错");
}
);
this.player.on(
ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS,
(e) => {
console.log("播放成功", e.streams);
}
);
this.player.on(
ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED,
(e) => {
console.log("offer anwser 交换失败", e);
this.stopVideo();
}
);
this.player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM, (s) => {
this.selfVideo.srcObject = s;
this.selfVideo.muted = true;
});
this.player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED, (s) => {
console.log("获取本地流失败");
});
this.player.on(
ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE,
(state) => {
console.log("当前状态==>", state);
}
);
this.player.on(
ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_OPEN,
(event) => {
console.log("rtc datachannel 打开 :", event);
}
);
this.player.on(
ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_MSG,
(event) => {
console.log("rtc datachannel 消息 :", event.data);
this.msgrecv = event.data;
}
);
this.player.on(
ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_ERR,
(event) => {
console.log("rtc datachannel 错误 :", event);
}
);
this.player.on(
ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_CLOSE,
(event) => {
console.log("rtc datachannel 关闭 :", event);
}
);
},
startVideo() {
this.stopVideo();
let res = this.resolution.match(/\d+/g);
let h = "50%";
let w = "50%";
this.start_play();
},
stopVideo() {
if (this.player) {
this.player.close();
this.player = null;
}
},
send() {
if (this.player) {
this.player.sendMsg(this.msgsend);
}
},
close() {
if (this.player) {
this.player.closeDataChannel();
}
},
},
};
</script>
<style>
.parent {
position: relative;
}
.operation-item img {
width: 24px;
height: 24px;
margin: 0 5px;
}
.direction-item {
display: flex;
justify-content: center;
margin-bottom: 10px;
}
.direction-item img {
width: 24px;
height: 24px;
margin: 0 5px;
}
</style>

@ -0,0 +1,283 @@
<template>
<div class="ptz-control">
<div class="direction-list">
<div class="operation-item">
<Icon
:icon="zoomSubIcon"
alt="Zoom In"
@mousedown="handleControl('zoomSub', 'start')"
@mouseup="handleControl('zoomSub', 'stop')"
@mouseleave="handleControl('zoomSub', 'stop')"
/>
</div>
<span>变倍</span>
<div class="operation-item">
<Icon
:icon="zoomAddIcon"
alt="Zoom Out"
@mousedown="handleControl('zoomAdd', 'start')"
@mouseup="handleControl('zoomAdd', 'stop')"
@mouseleave="handleControl('zoomAdd', 'stop')"
/>
</div>
<div class="operation-item">
<Icon
:icon="focusSubIcon"
alt="Focus In"
@mousedown="handleControl('focusSub', 'start')"
@mouseup="handleControl('focusSub', 'stop')"
@mouseleave="handleControl('focusSub', 'stop')"
/>
</div>
<span>变焦</span>
<div class="operation-item">
<Icon
:icon="focusAddIcon"
alt="Focus Out"
@mousedown="handleControl('focusAdd', 'start')"
@mouseup="handleControl('focusAdd', 'stop')"
@mouseleave="handleControl('focusAdd', 'stop')"
/>
<span></span>
</div>
<div class="operation-item">
<Icon
:icon="irisSubIcon"
alt="Iris Open"
@mousedown="handleControl('irisSub', 'start')"
@mouseup="handleControl('irisSub', 'stop')"
@mouseleave="handleControl('irisSub', 'stop')"
/>
</div>
<span>光圈</span>
<div class="operation-item">
<Icon
:icon="irisAddIcon"
alt="Iris Close"
@mousedown="handleControl('irisAdd', 'start')"
@mouseup="handleControl('irisAdd', 'stop')"
@mouseleave="handleControl('irisAdd', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="leftUpIcon"
alt="Left Up"
@mousedown="handleControl('leftUp', 'start')"
@mouseup="handleControl('leftUp', 'stop')"
@mouseleave="handleControl('leftUp', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="upIcon"
alt="Up"
@mousedown="handleControl('up', 'start')"
@mouseup="handleControl('up', 'stop')"
@mouseleave="handleControl('up', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="rightUpIcon"
alt="Right Up"
@mousedown="handleControl('rightUp', 'start')"
@mouseup="handleControl('rightUp', 'stop')"
@mouseleave="handleControl('rightUp', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="leftIcon"
alt="Left"
@mousedown="handleControl('left', 'start')"
@mouseup="handleControl('left', 'stop')"
@mouseleave="handleControl('left', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="centerIcon"
alt="Center"
/>
</div>
<div class="direction-item">
<Icon
:icon="rightIcon"
alt="Right"
@mousedown="handleControl('right', 'start')"
@mouseup="handleControl('right', 'stop')"
@mouseleave="handleControl('right', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="leftDownIcon"
alt="Left Down"
@mousedown="handleControl('leftDown', 'start')"
@mouseup="handleControl('leftDown', 'stop')"
@mouseleave="handleControl('leftDown', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="downIcon"
alt="Down"
@mousedown="handleControl('down', 'start')"
@mouseup="handleControl('down', 'stop')"
@mouseleave="handleControl('down', 'stop')"
/>
</div>
<div class="direction-item">
<Icon
:icon="rightDownIcon"
alt="Right Down"
@mousedown="handleControl('rightDown', 'start')"
@mouseup="handleControl('rightDown', 'stop')"
@mouseleave="handleControl('rightDown', 'stop')"
/>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios' // axios
import { config } from '@/config/axios/config'
import {CameraControlApi} from '@/api/cameraControl/camera'
const { result_code, base_url, request_timeout } = config
export default {
props: {
cameraId: {
default: 2
}
},
setup(props) {
console.log(props.cameraId)
//
const zoomSubIcon = ref('ep:zoom-out') //
const zoomAddIcon = ref('ep:zoom-in') //
const focusSubIcon = ref('ep:minus') //
const focusAddIcon = ref('ep:plus') //
const irisSubIcon = ref('ep:minus') //
const irisAddIcon = ref('ep:plus') //
const leftUpIcon = ref('ep:top-left') //
const upIcon = ref('ep:arrow-up') //
const rightUpIcon = ref('ep:top-right') //
const leftIcon = ref('ep:arrow-left') //
const centerIcon = ref('ep:aim') //
const rightIcon = ref('ep:arrow-right') //
const leftDownIcon = ref('ep:bottom-left') //
const downIcon = ref('ep:arrow-down') //
const rightDownIcon = ref('ep:bottom-right') //
const activeIcons = {
zoomSub: 'ep:zoom-out-active',
zoomAdd: 'ep:zoom-in-active',
focusSub: 'ep:minus-active',
focusAdd: 'ep:plus-active',
irisSub: 'ep:minus-active',
irisAdd: 'ep:plus-active',
leftUp: 'ep:top-left-active',
up: 'ep:arrow-up-active',
rightUp: 'ep:top-right-active',
left: 'ep:arrow-left-active',
center: 'ep:aim-active',
right: 'ep:arrow-right-active',
leftDown: 'ep:bottom-left-active',
down: 'ep:arrow-down-active',
rightDown: 'ep:bottom-right-active'
}
//
const handleControl = (action, state) => {
console.log(`${action}-${state}`)
const iconRef = `${action}Icon`
if (state === 'start') {
if (activeIcons[action]) {
eval(`${iconRef}.value = activeIcons[action]`)
}
} else if (state === 'stop') {
eval(`${iconRef}.value = '${eval(iconRef).value.replace('-active', '')}'`)
}
const data ={"id":props.cameraId}
CameraControlApi.cameraControl(`/camera/control/${action}/${state}`,data)
}
const close = () => {
//
console.log('关闭控制面板')
}
return {
zoomSubIcon,
zoomAddIcon,
focusSubIcon,
focusAddIcon,
irisSubIcon,
irisAddIcon,
leftUpIcon,
upIcon,
rightUpIcon,
leftIcon,
centerIcon,
rightIcon,
leftDownIcon,
downIcon,
rightDownIcon,
handleControl,
close
}
}
}
</script>
<style scoped>
.ptz-control-panel {
position: relative; /* 改为绝对定位 */
bottom: 20px;
right: 20px;
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid #ccc;
padding: 10px;
border-radius: 5px;
z-index: 1000; /* 确保浮在最上层 */
}
.operation-list {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 10px;
}
.operation-item {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 5px;
}
.operation-item img {
width: 40px;
height: 40px;
cursor: pointer;
}
.direction-list {
display: grid;
grid-template-columns: repeat(3, 40px);
grid-gap: 5px;
}
.direction-item img {
width: 40px;
height: 40px;
cursor: pointer;
}
</style>

@ -29,42 +29,42 @@ const routes = [{
},
component: () => import('@/views/index'),
children: [
{
path: 'videoWall1`',
name: 'videoWall1`',
meta: {
icon: 'desktop',
name: '视频墙1'
},
component: () => import('@/views/videoWall/index'),
},
{
path: 'videoWall2',
name: 'videoWall2',
meta: {
icon: 'desktop',
name: '视频墙2'
},
component: () => import('@/views/videoWall2/index'),
},
{
path: 'videoWall3',
name: 'videoWall3',
meta: {
icon: 'desktop',
name: '视频墙3'
},
component: () => import('@/views/videoWall3/index'),
},
{
path: 'videoWall4',
name: 'videoWall4',
meta: {
icon: 'desktop',
name: '视频墙4'
},
component: () => import('@/views/videoWall4/index'),
},
// {
// path: 'videoWall1`',
// name: 'videoWall1`',
// meta: {
// icon: 'desktop',
// name: '视频墙1'
// },
// component: () => import('@/views/videoWall/index'),
// },
// {
// path: 'videoWall2',
// name: 'videoWall2',
// meta: {
// icon: 'desktop',
// name: '视频墙2'
// },
// component: () => import('@/views/videoWall2/index'),
// },
// {
// path: 'videoWall3',
// name: 'videoWall3',
// meta: {
// icon: 'desktop',
// name: '视频墙3'
// },
// component: () => import('@/views/videoWall3/index'),
// },
// {
// path: 'videoWall4',
// name: 'videoWall4',
// meta: {
// icon: 'desktop',
// name: '视频墙4'
// },
// component: () => import('@/views/videoWall4/index'),
// },
{
path: 'realTimeMonitoring',
name: 'realTimeMonitoring',

@ -50,10 +50,12 @@
class="config-model"
>
<video width="900"
<camera style="width:900px;
height:600px" :cameraId="`${id}`" :ptzShow="false"></camera>
<!-- <video width="900"
height="600"
:id="`config-camera${id}`"
autoplay muted ></video>
autoplay muted ></video> -->
<div class="operation-list">
<div class="operation-item">
<img :src="zoomSubUrl" alt="" @mousedown="zoomDecStart"
@ -129,6 +131,7 @@
</div>
</template>
<script>
import camera from '../../components/camera/camera.vue';
var formItemAcrossLayout = { //formItemAcrossLayoutitem
labelCol: { span: 8 }, //lable
wrapperCol: { span: 14 }, //input
@ -151,6 +154,10 @@ const columns = [
];
import WebRtcPlayer from "../../../public/static/webrtcplayer"
export default {
components: {
camera
//VideoPlayer
},
props:[ 'visible', 'modelType', 'modelData'],
watch: {
//visibleisShowprops
@ -241,9 +248,7 @@ export default {
}
},
destroy(){
if(this.player != null){
this.player.destroy()
}
},
methods: {
@ -292,7 +297,7 @@ export default {
this.$router.go(-1);
}else if(this.type=='config') {
this.$router.go(-1);
this.player.destroy();
}
},

@ -78,19 +78,13 @@
</span>
<span slot="videoPath1" slot-scope="text">
<a-button type="link" @click="showModel(text)">
查看视频
视频录像
</a-button>
</span>
<!-- <span slot="videoPath2" slot-scope="text">
<a-button type="link" v-if="text.videoPath2" @click="showModel(text.videoPath2)">
查看视频
</a-button>
<span v-else>
--
</span>
</span> -->
</a-table>
<Model
:visible.sync="visible"
:vid1.sync="vid1"
@ -99,6 +93,7 @@
:vid4.sync="vid4"
@close="closeModel"
/>
</div>
</template>
<script>
@ -139,11 +134,6 @@ export default {
dataIndex: "streetName",
width: 90
},
{
title: "货架类型",
dataIndex: "streetType",
scopedSlots: {customRender: 'streetType'}
},
{
title: "货位",
dataIndex: "goodsLocation",
@ -152,22 +142,6 @@ export default {
title: "时间",
dataIndex: "startTime",
},
{
title: "照片",
// dataIndex: "pic",
scopedSlots: {customRender: 'pics'},
width:320,
},
{
title: "视频状态",
// dataIndex: "status",
scopedSlots: {customRender: 'status'},
width: 90
},
{
title: "工单时长",
dataIndex: "timeLength",
},
{
title: "视频录像",
// dataIndex: "videoPath1",
@ -176,6 +150,7 @@ export default {
],
visible: false,
visiblevga: false,
vid1: '',
vid3: '',
vid4: '',
@ -278,15 +253,29 @@ export default {
showModel(record) {
this.visible = true
this.vid1 = videoUrl + record.videoPath1;
console.log(this.vid1)
this.vid2 = videoUrl + record.videoPath2;
this.vid3 = videoUrl + record.videoPath2+".mp4";
this.vid4 = videoUrl + record.videoPath2+".mp4";
this.vid3 = videoUrl + record.videoPath3;
this.vid4 = videoUrl + record.videoPath4;
this.vid1 = record.videoPath1;
this.vid2 = record.videoPath2;
this.vid3 = record.videoPath3;
this.vid4 = record.videoPath4;
},
showModelVga(record) {
this.visiblevga = true
this.vid1 = videoUrl + record.videoPath1;
this.vid2 = videoUrl + record.videoPath2;
this.vid3 = videoUrl + record.videoPath3;
this.vid4 = videoUrl + record.videoPath4;
},
closeModel(visible, data) {
console.log(visible);
this.visible = visible
this.vid1 = data
},
closeModelvga(visiblevga, data) {
this.visiblevga = visiblevga
},
reset() {
this.queryParam.startTimestamp = ""

@ -1,6 +1,8 @@
<template>
<div v-if="isShow">
<!-- a-slider 组件 -->
<a-modal
width="1000px"
v-model="isShow"
@cancel="handleCancel"
:footer="null"
@ -15,21 +17,25 @@
</div>
</div>
<div class="test_two_box">
<video
class="video-js"
:autoplay="true"
controls
ref="video1"
@timeupdate="syncTime($event)"
>
<source
:src="video1"
type="video/mp4"
>
</video>
<video
<video
class="video-js"
:autoplay="true"
controls
:controls="!checked"
ref="video2"
>
<source
:src="video2"
@ -37,10 +43,10 @@
>
</video>
<video
class="video-js"
:autoplay="true"
controls
:controls="!checked"
ref="video3"
>
<source
:src="video3"
@ -48,17 +54,27 @@
>
</video>
<video
class="video-js"
:autoplay="true"
controls
:controls="!checked"
ref="video4"
>
<source
:src="video4"
type="video/mp4"
>
</video>
<div>
<a-button
type="primary"
@click="handleCancelButton"
>
{{!checked?"统一控制":"取消统一控制"}}
</a-button>
</div>
</div>
</a-modal>
</div>
</template>
@ -67,6 +83,7 @@
.across-layout {
display: flex;
}
width: 1600px;
}
.ant-btn {
@ -75,7 +92,7 @@
}
.video-js {
width: 100%;
width: 50%;
height: 300px;
}
@ -96,59 +113,85 @@
</style>
<script>
export default {
props: ['visible', 'vid1','vid2', 'vid3','vid4'],
props: ["visible", "vid1", "vid2", "vid3", "vid4"],
watch: {
//visibleisShowprops
visible: function (newVal) {
console.log(newVal);
this.isShow = newVal; //newValvisible
// newVal && this.showConfirm(); //newValshowConfirm
},
vid1: function (newVal) {
this.video1 = newVal
console.log(this.video1)
this.$nextTick(() => { //this.$nextTick
})
console.log(newVal);
this.video1 = newVal;
this.$nextTick(() => {
//this.$nextTick
});
},
vid2: function (newVal) {
this.video2 = newVal;
this.$nextTick(() => {
//this.$nextTick
});
},
vid3: function (newVal) {
this.video3 = newVal
console.log(this.video3)
this.$nextTick(() => { //this.$nextTick
})
this.video3 = newVal;
this.$nextTick(() => {
//this.$nextTick
});
},
vid4: function (newVal) {
this.video4 = newVal
console.log(this.video4)
this.$nextTick(() => { //this.$nextTick
})
this.video4 = newVal;
this.$nextTick(() => {
//this.$nextTick
});
},
},
data() {
return {
isShow: false,
confirmLoading: false,
video1: '',
video2: '',
video3: '',
video4: '',
video1: "",
video2: "",
video3: "",
video4: "",
video1CurrentTime: 0, // 1
video2CurrentTime: 0, // 2
video3CurrentTime: 0, // 3
video4CurrentTime: 0, // 4
maxTime: 0, //
checked: false, //
sliderValue: 0, // a-slider
};
},
mounted() {
},
mounted() {},
methods: {
handleCancelButton() {
this.checked = !this.checked;
},
handleCancel() {
this.$emit('close', false, '')
},
syncTime(event) {
if (this.checked) {
const changedVideo = event.target; //
const currentTime = changedVideo.currentTime; //
//
const allVideos = [this.$refs.video1, this.$refs.video2,this.$refs.video3, this.$refs.video4];
//
allVideos.forEach((video) => {
if (video !== changedVideo) {
video.currentTime = currentTime;
}
});
}
},
onChange() {
console.log("onChange");
},
},
};
</script>

@ -4,7 +4,7 @@
<a-tab-pane :key="item.streetId.toString()" :tab="item.streetName" v-for="item in realTimeListData"></a-tab-pane>
</a-tabs>
<!-- {{cameras.length}} {{ selectTab.videoStyleRow }} * {{ selectTab.videoStyleColumn }}-->
<a-row :gutter="20">
<a-row :gutter="20" style="height: 42vh;">
<a-col
v-for="(item, index) in cameras.slice(0, selectTab.videoStyleRow * selectTab.videoStyleColumn)"
:span="24 / selectTab.videoStyleColumn"
@ -15,9 +15,10 @@
marginBottom: ((selectTab.videoStyleRow * selectTab.videoStyleColumn - index - 1) >= selectTab.videoStyleColumn) ? '20px': 0
}"
>
<div style="position: relative;width: 100%;">
<video :style="{ height: videoHeight,}" :id="`camera${item.id}`" autoplay muted controls>
</video>
<div style="position: relative;width: 100%;height:100%">
<!-- <video :style="{ height: videoHeight,}" :id="`camera${item.id}`" autoplay muted controls>
</video> -->
<camera :cameraId="`${item.id}`"></camera>
<div class="operation-list">
<div class="operation-item">
<img :src="zoomSubUrl" alt="" @mousedown=zoomDecStart($event,item.id) @mouseleave=zoomDecStop($event,item.id)
@ -62,7 +63,7 @@
</a-col>
</a-row>
<a-row :gutter="20">
<a-row :gutter="20" style="height: 42vh;">
<a-col
v-for="(item, index) in cameras.slice(2, selectTab.videoStyleRow * selectTab.videoStyleColumn*2)"
:span="24 / selectTab.videoStyleColumn"
@ -73,10 +74,9 @@
marginBottom: ((selectTab.videoStyleRow * selectTab.videoStyleColumn - index - 1) >= selectTab.videoStyleColumn) ? '20px': 0
}"
>
<div style="position: relative;width: 100%;">
<video :style="{ height: videoHeight,}" :id="`camera${item.id}`" autoplay muted controls>
</video>
<div class="operation-list">
<div style="position: relative;width: 100%;height:100%">
<camera :cameraId="`${item.id}`" :ptzShow="true"></camera>
<!-- <div class="operation-list">
<div class="operation-item">
<img :src="zoomSubUrl" alt="" @mousedown=zoomDecStart($event,item.id) @mouseleave=zoomDecStop($event,item.id)
>
@ -114,7 +114,7 @@
<img :src="rightDownUrl" alt="" @mousedown=rightDownStart($event,item.id)
@mouseup=rightDownStop($event,item.id) @mouseleave=rightDownStop($event,item.id)>
</div>
</div>
</div> -->
</div>
@ -124,11 +124,12 @@
</template>
<script>
import WebRtcPlayer from "../../../public/static/webrtcplayer"
import Camera from '../../components/camera/camera.vue';
export default {
name: "realTimeMonitoring",
components: {
Camera
//VideoPlayer
},
data() {

Loading…
Cancel
Save