Compare commits

...

8 Commits

Author SHA1 Message Date
阿斌
42259f9f2b
Pre Merge pull request !36 from 阿斌/N/A 2026-05-25 14:17:20 +00:00
648540858
676a59e5f7
Merge pull request #2152 from T0n0T/fix/cloud-record-websocket-and-ptz-cruise-preset
fix: add WebSocket sub_filter for mp4_record and fix ptzCruising preset label
2026-05-25 22:16:29 +08:00
648540858
6916ecacb2
Merge pull request #2157 from xiaoQQya/fix/postgresql
fix: 修复 postgresql 数据库语法错误
2026-05-25 22:10:39 +08:00
lin
fac2195ace 重构播放器分享功能,为各处播放器增加播放器切换能力 2026-05-25 22:05:15 +08:00
xiaoQQya
49021c443f fix: 修复 postgresql 数据库语法错误 2026-05-25 15:54:28 +08:00
T0n0T
bc3cde6d52 fix: add value-key for el-select to display preset label correctly
When presetName is empty, the el-select trigger area failed to show
the fallback presetId without value-key being set explicitly.
2026-05-15 18:09:05 +08:00
T0n0T
477172f3af fix: add WebSocket sub_filter for mp4_record and fix ptzCruising preset label
- Add ws:// and wss:// sub_filter entries for mp4_record in nginx template
  to allow WebSocket playback of cloud recordings through nginx proxy
- Fix ptzCruising dropdown preset label to fallback to presetId when
  presetName is empty, preventing blank option display
2026-05-15 16:13:04 +08:00
阿斌
da98101aac
update src/main/resources/civilCode.csv.
行政规划错误。江苏南通海门市,修改为海门区,浙江杭州删除下城区、江干区,新增钱塘区,临平区

Signed-off-by: 阿斌 <38912748@qq.com>
2024-12-15 08:58:42 +00:00
25 changed files with 304 additions and 201 deletions

View File

@ -34,6 +34,10 @@ server {
sub_filter "http://$original_host:80/mp4_record" "mp4_record";
sub_filter "https://$original_host/mp4_record" "mp4_record";
sub_filter "https://$original_host:443/mp4_record" "mp4_record";
sub_filter "ws://$original_host/mp4_record" "mp4_record";
sub_filter "ws://$original_host:80/mp4_record" "mp4_record";
sub_filter "wss://$original_host/mp4_record" "mp4_record";
sub_filter "wss://$original_host:443/mp4_record" "mp4_record";
# 设置为off表示替换所有匹配项而不仅仅是第一个
sub_filter_once off;

View File

@ -95,5 +95,4 @@ public class RegisterResponseProcessor extends SIPResponseProcessorAbstract {
}
}
}
}

View File

@ -139,7 +139,7 @@ public class RtpServerServiceImpl implements IReceiveRtpServerService {
ssrc = ssrcFactory.getPlaySsrc(mediaServer);
}
String streamId = String.format("%08x", Long.parseLong(ssrc)).toLowerCase();
String streamId = String.format("%08x", Long.parseLong(ssrc)).toUpperCase();
String streamReplace = String.format("%s_%s", device.getDeviceId(), channel.getDeviceId());
int tcpMode = device.getStreamMode().equals("TCP-ACTIVE")? 2: (device.getStreamMode().equals("TCP-PASSIVE")? 1:0);
@ -309,7 +309,7 @@ public class RtpServerServiceImpl implements IReceiveRtpServerService {
// 收流超时
// 关闭收流端口
String closeStreamId = rtpServerParam.getMediaServer().isRtpEnable()
? String.format("%08x", rtpServerParam.getSsrc()) : rtpServerParam.getStreamId();
? String.format("%08x", rtpServerParam.getSsrc()).toUpperCase() : rtpServerParam.getStreamId();
mediaServerService.closeRTPServer(rtpServerParam.getMediaServer(), rtpServerParam.getApp(), closeStreamId);
subscribe.removeSubscribe(rtpHook);
callback.run(InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getCode(), InviteErrorCode.ERROR_FOR_STREAM_TIMEOUT.getMsg(), null);
@ -324,8 +324,15 @@ public class RtpServerServiceImpl implements IReceiveRtpServerService {
int rtpServerPort;
if (rtpServerParam.getMediaServer().isRtpEnable()) {
String zlmStreamId = String.format("%08x", rtpServerParam.getSsrc());
Long checkSsrc = rtpServerParam.isSsrcCheck() ? rtpServerParam.getSsrc() : 0L;
String zlmStreamId;
long checkSsrc;
if (rtpServerParam.getSsrc() != null) {
zlmStreamId = String.format("%08x", rtpServerParam.getSsrc()).toUpperCase();
checkSsrc = rtpServerParam.isSsrcCheck() ? rtpServerParam.getSsrc() : 0L;
}else {
zlmStreamId = rtpServerParam.getStreamId();
checkSsrc = 0L;
}
rtpServerPort = mediaServerService.createRTPServer(rtpServerParam.getMediaServer(), rtpServerParam.getApp(), zlmStreamId, checkSsrc, rtpServerParam.getPort(), rtpServerParam.isOnlyAuto(),
rtpServerParam.isDisableAudio(), rtpServerParam.isReUsePort(), rtpServerParam.getTcpMode());
} else {

View File

@ -861,7 +861,7 @@
320623,如东县,3206
320681,启东市,3206
320682,如皋市,3206
320684,海门,3206
320684,海门,3206
320685,海安市,3206
3207,连云港市,32
320703,连云区,3207
@ -918,8 +918,6 @@
33,浙江省,
3301,杭州市,33
330102,上城区,3301
330103,下城区,3301
330104,江干区,3301
330105,拱墅区,3301
330106,西湖区,3301
330108,滨江区,3301
@ -927,6 +925,8 @@
330110,余杭区,3301
330111,富阳区,3301
330112,临安区,3301
330113,临平区,3301
330114,钱塘区,3301
330122,桐庐县,3301
330127,淳安县,3301
330182,建德市,3301

1 编号 名称 上级
861 320623 如东县 3206
862 320681 启东市 3206
863 320682 如皋市 3206
864 320684 海门市 海门区 3206
865 320685 海安市 3206
866 3207 连云港市 32
867 320703 连云区 3207
918 33 浙江省
919 3301 杭州市 33
920 330102 上城区 3301
330103 下城区 3301
330104 江干区 3301
921 330105 拱墅区 3301
922 330106 西湖区 3301
923 330108 滨江区 3301
925 330110 余杭区 3301
926 330111 富阳区 3301
927 330112 临安区 3301
928 330113 临平区 3301
929 330114 钱塘区 3301
930 330122 桐庐县 3301
931 330127 淳安县 3301
932 330182 建德市 3301

View File

@ -7,7 +7,7 @@ import getPageTitle from '@/utils/get-page-title'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
const whiteList = ['/login', '/play/share'] // no redirect whitelist
router.beforeEach(async(to, from, next) => {
// start progress bar

View File

@ -288,15 +288,10 @@ export const constantRoutes = [
]
},
{
path: '/play/wasm/:url',
name: 'wasmPlayer',
path: '/play/share',
name: 'sharePlayer',
hidden: true,
component: () => import('@/views/common/jessibuca.vue')
},
{
path: '/play/rtc/:url',
name: 'rtcPlayer',
component: () => import('@/views/common/rtcPlayer.vue')
component: () => import('@/views/common/share.vue')
},
// 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true }

View File

@ -164,7 +164,6 @@
<div v-else-if="playbackStreamInfo">
<h265web
ref="playbackPlayer"
:video-url="playbackVideoUrl"
:height="'400px'"
:show-button="false"
:has-audio="true"
@ -315,6 +314,11 @@ export default {
this.playbackVideoUrl = data['ws_flv']
}
this.playbackLoading = false
this.$nextTick(() => {
if (this.$refs.playbackPlayer) {
this.$refs.playbackPlayer.play(this.playbackVideoUrl)
}
})
}).catch(err => {
this.playbackLoading = false
this.playbackError = (err && err.msg) ? err.msg : '回放请求失败,请检查通道是否有该时段录像'

View File

@ -60,7 +60,6 @@
</div>
<h265web
ref="recordVideoPlayer"
:video-url="videoUrl"
:height="'calc(100vh - 250px)'"
:show-button="false"
:has-audio="true"
@ -465,6 +464,11 @@ export default {
this.streamInfo = data
this.videoUrl = this.getUrlByStreamInfo()
this.hasAudio = this.streamInfo.tracks && this.streamInfo.tracks.length > 1
this.$nextTick(() => {
if (this.$refs.recordVideoPlayer) {
this.$refs.recordVideoPlayer.play(this.videoUrl)
}
})
})
}
},

View File

@ -1,19 +1,28 @@
<template>
<div id="cloudRecordPlayer" style="height: 100%">
<div class="cloud-record-playBox" :style="playBoxStyle">
<h265web v-if="playerType === 'H265web'" ref="recordVideoPlayer" :video-url="videoUrl" :height="'calc(100% - 250px)'" :show-button="false" @playTimeChange="showPlayTimeChange" @playStatusChange="playingChange"/>
<jessibucaPlayer
v-if="playerType === 'Jessibuca'"
ref="recordVideoPlayer"
:height="'calc(100% - 250px)'"
:show-button="false"
:video-url="videoUrl"
@playTimeChange="showPlayTimeChange"
@playStatusChange="playingChange"
fluent
autoplay
live
/>
<rtcPlayer
v-if="playerType === 'WebRTC'"
ref="recordVideoPlayer"
:has-audio="true"
:show-controls="false"
style="height: calc(100% - 250px)"
autoplay
@playTimeChange="showPlayTimeChange"
@playStatusChange="playingChange"
/>
<h265web v-if="playerType === 'H265web'" ref="recordVideoPlayer" :height="'calc(100% - 250px)'" :show-button="false" @playTimeChange="showPlayTimeChange" @playStatusChange="playingChange"/>
</div>
<div class="cloud-record-player-option-box">
<div class="cloud-record-show-time">
@ -70,10 +79,11 @@
<div class="cloud-record-record-play-control-item record-play-control-player">
<el-dropdown @command="changePlayerType" :popper-append-to-body='false' >
<a target="_blank" class="cloud-record-record-play-control-item record-play-control-speed" title="选择播放器">{{ playerType }}</a>
<a target="_blank" class="cloud-record-record-play-control-item record-play-control-speed" title="选择播放器">{{ playerLabel }}</a>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="H265web" >H265web</el-dropdown-item>
<el-dropdown-item command="Jessibuca" >Jessibuca</el-dropdown-item>
<el-dropdown-item command="WebRTC" >WebRTC</el-dropdown-item>
<el-dropdown-item command="H265web" >H265web</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
@ -88,18 +98,18 @@
<script>
import h265web from '../common/h265web.vue'
import jessibucaPlayer from '@/views/common/jessibuca.vue'
import rtcPlayer from '../common/rtcPlayer.vue'
import moment from 'moment'
import momentDurationFormatSetup from 'moment-duration-format'
import screenfull from 'screenfull'
import jessibucaPlayer from '@/views/common/jessibuca.vue'
momentDurationFormatSetup(moment)
export default {
name: 'CloudRecordPlayer',
components: {
jessibucaPlayer,
h265web
jessibucaPlayer, rtcPlayer, h265web
},
props: ['showListCallback', 'showNextCallback', 'showLastCallback', 'lastDiable', 'nextDiable'],
data() {
@ -119,6 +129,11 @@ export default {
playing: false,
initTime: null,
playerType: 'Jessibuca',
playerUrls: {
Jessibuca: ['ws_flv', 'wss_flv'],
WebRTC: ['rtc', 'rtcs'],
H265web: ['ws_flv', 'wss_flv']
},
playSpeedRange: [1, 2, 4, 6, 8, 16, 20]
}
},
@ -157,6 +172,10 @@ export default {
}else {
return ''
}
},
playerLabel() {
const labels = { Jessibuca: 'Jessibuca', WebRTC: 'WebRTC', H265web: 'H265Web' }
return labels[this.playerType] || 'Jessibuca'
}
},
created() {
@ -168,9 +187,6 @@ export default {
this.$destroy('recordVideoPlayer')
},
methods: {
changePlayer(command) {
this.playerType = command
},
timeProcessMouseup(event) {
this.isMousedown = false
},
@ -228,21 +244,15 @@ export default {
if (this.playerType === playerType) {
return
}
let streamInfo = this.streamInfo
let videoUrl = this.videoUrl
this.$refs.recordVideoPlayer.destroy()
this.seekRecord(0, () => {
this.playerType = playerType
if (this.streamInfo) {
this.videoUrl = this.getUrlByStreamInfo()
this.$nextTick(() => {
setTimeout(() => {
this.playerType = playerType
this.playerTime = 0
this.streamInfo = streamInfo
this.videoUrl = videoUrl
}, 1000)
if (this.$refs.recordVideoPlayer) {
this.$refs.recordVideoPlayer.play(this.videoUrl)
}
})
})
}
},
seekBackward() {
// 退
@ -290,15 +300,31 @@ export default {
this.isFullScreen = true
},
setStreamInfo(streamInfo, timeLen, startTime) {
const keys = this.playerUrls[this.playerType]
if (location.protocol === 'https:') {
this.videoUrl = streamInfo['wss_flv']
this.videoUrl = streamInfo[keys[1]]
} else {
this.videoUrl = streamInfo['ws_flv']
this.videoUrl = streamInfo[keys[0]]
}
console.log(location.protocol)
this.streamInfo = streamInfo
this.timeLen = timeLen
this.startTime = startTime
this.$nextTick(() => {
if (this.$refs.recordVideoPlayer) {
this.$refs.recordVideoPlayer.play(this.videoUrl)
}
})
},
getUrlByStreamInfo() {
if (!this.streamInfo) return ''
const keys = this.playerUrls[this.playerType]
if (location.protocol === 'https:') {
this.videoUrl = this.streamInfo[keys[1]]
} else {
this.videoUrl = this.streamInfo[keys[0]]
}
return this.videoUrl
},
seekRecord(playSeekValue, callback) {
this.$store.dispatch('cloudRecord/seek', {

View File

@ -23,7 +23,6 @@
v-if="activePlayer === 'jessibuca'"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
@ -37,7 +36,6 @@
v-if="activePlayer === 'webRTC'"
ref="webRTC"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
@ -51,7 +49,6 @@
<h265web
v-if="activePlayer === 'h265web'"
ref="h265web"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
@ -67,7 +64,6 @@
v-if="Object.keys(this.player).length == 1 && this.player.jessibuca"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
@ -77,9 +73,8 @@
/>
<rtc-player
v-if="Object.keys(this.player).length == 1 && this.player.webRTC"
ref="jessibuca"
ref="rtcPlayer"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
@ -90,9 +85,8 @@
/>
<h265web
v-if="Object.keys(this.player).length == 1 && this.player.h265web"
ref="jessibuca"
ref="h265web"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
@ -430,9 +424,12 @@ export default {
},
computed: {
getPlayerShared: function() {
const typeMap = { jessibuca: 0, webRTC: 1, h265web: 2 }
const type = typeMap[this.activePlayer] || 0
const baseUrl = window.location.origin + '/#/play/share?type=' + type + '&url=' + encodeURIComponent(this.videoUrl)
return {
sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
sharedIframe: '' + window.location.origin + '<iframe src="/public#/play/wasm/"></iframe>' + encodeURIComponent(this.videoUrl) + '',
sharedUrl: baseUrl,
sharedIframe: '<iframe src="' + baseUrl + '"></iframe>',
sharedRtmp: this.videoUrl
}
}

View File

@ -7,21 +7,21 @@
v-if="Object.keys(this.player).length > 1">
<el-tab-pane label="Jessibuca" name="jessibuca">
<jessibucaPlayer v-if="activePlayer === 'jessibuca'" ref="jessibuca" :visible.sync="showVideoDialog"
:videoUrl="videoUrl" :error="videoError" :message="videoError"
:error="videoError" :message="videoError"
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
</el-tab-pane>
<el-tab-pane label="WebRTC" name="webRTC">
<rtc-player v-if="activePlayer === 'webRTC'" ref="webRTC" :visible.sync="showVideoDialog"
:videoUrl="videoUrl" :error="videoError" :message="videoError" height="100px"
:error="videoError" :message="videoError" height="100px"
:hasAudio="hasAudio" fluent autoplay live></rtc-player>
</el-tab-pane>
<el-tab-pane label="h265web">h265web敬请期待</el-tab-pane>
</el-tabs>
<jessibucaPlayer v-if="Object.keys(this.player).length == 1 && this.player.jessibuca" ref="jessibuca"
:visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError"
:visible.sync="showVideoDialog" :error="videoError" :message="videoError"
:hasAudio="hasAudio" fluent autoplay live></jessibucaPlayer>
<rtc-player v-if="Object.keys(this.player).length == 1 && this.player.webRTC" ref="jessibuca"
:visible.sync="showVideoDialog" :videoUrl="videoUrl" :error="videoError" :message="videoError"
:visible.sync="showVideoDialog" :error="videoError" :message="videoError"
height="100px" :hasAudio="hasAudio" fluent autoplay live></rtc-player>
</div>
@ -266,9 +266,12 @@ export default {
},
computed: {
getPlayerShared: function () {
const typeMap = { jessibuca: 0, webRTC: 1, h265web: 2 }
const type = typeMap[this.activePlayer] || 0
const baseUrl = window.location.origin + '/#/play/share?type=' + type + '&url=' + encodeURIComponent(this.videoUrl)
return {
sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
sharedIframe: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></iframe>',
sharedUrl: baseUrl,
sharedIframe: '<iframe src="' + baseUrl + '"></iframe>',
sharedRtmp: this.videoUrl
};
}
@ -359,6 +362,13 @@ export default {
this.activePlayer = tab.name;
this.videoUrl = this.getUrlByStreamInfo()
console.log(this.videoUrl)
if (this.$refs[this.activePlayer]) {
this.$refs[this.activePlayer].play(this.videoUrl)
} else {
this.$nextTick(() => {
this.$refs[this.activePlayer].play(this.videoUrl)
})
}
},
openDialog: function (tab, deviceId, channelId, param) {
if (this.showVideoDialog) {

View File

@ -26,11 +26,11 @@
<el-form v-if="selectPresetVisible" size="mini" :inline="true">
<el-form-item>
<el-select v-model="selectPreset" placeholder="请选择预置点">
<el-select v-model="selectPreset" value-key="presetId" placeholder="请选择预置点">
<el-option
v-for="item in allPresetList"
:key="item.presetId"
:label="item.presetName"
:label="item.presetName ? item.presetName : item.presetId"
:value="item"
/>
</el-select>

View File

@ -64,32 +64,14 @@ export default {
}
},
watch: {
videoUrl(newData, oldData) {
this.play(newData)
},
playing(newData, oldData) {
this.$emit('playStatusChange', newData)
},
immediate: true
},
mounted() {
const paramUrl = decodeURIComponent(this.$route.params.url)
window.onresize = () => {
this.updatePlayerDomSize()
}
this.btnDom = document.getElementById('buttonsBox')
console.log('初始化时的地址为: ' + paramUrl)
if (paramUrl) {
this.play(this.videoUrl)
}
},
mounted() {},
destroyed() {
if (h265webPlayer[this._uid]) {
h265webPlayer[this._uid].destroy()
}
this.playing = false
this.loaded = false
this.playerLoading = false
this.destroy()
},
methods: {
updatePlayerDomSize() {
@ -244,6 +226,9 @@ export default {
this.playing = false
this.err = ''
},
stop: function() {
this.destroy()
},
fullscreenSwich: function() {
const isFull = this.isFullscreen()
if (isFull) {

View File

@ -50,38 +50,14 @@ export default {
playerTime: 0,
rotate: 0,
vod: true, //
forceNoOffscreen: false
forceNoOffscreen: false,
localVideoUrl: this.videoUrl
}
},
created() {
if (this.$route.params.url) {
const paramUrl = decodeURIComponent(this.$route.params.url)
console.log(paramUrl)
if (!this.videoUrl) {
this.videoUrl = paramUrl
}
}
this.btnDom = document.getElementById('buttonsBox')
},
mounted() {
if (this.videoUrl) {
this.$nextTick(() => {
this.play(this.videoUrl)
})
}
},
watch: {
videoUrl: {
handler(newVal) {
if (newVal) {
this.$nextTick(() => {
this.play(newVal)
})
}
},
immediate: false
}
},
mounted() {},
destroyed() {
if (jessibucaPlayer[this._uid]) {
jessibucaPlayer[this._uid].videoPTS = 0
@ -223,7 +199,10 @@ export default {
console.warn('Jessibuca -> invalid url, skip play')
return
}
this.videoUrl = url
if (this.playing) {
this.stop()
}
this.localVideoUrl = url
console.log('Jessibuca -> url: ', url)
if (!jessibucaPlayer[this._uid]) {
this.create()

View File

@ -26,11 +26,11 @@
<el-form v-if="selectPresetVisible" size="mini" :inline="true">
<el-form-item>
<el-select v-model="selectPreset" placeholder="请选择预置点">
<el-select v-model="selectPreset" value-key="presetId" placeholder="请选择预置点">
<el-option
v-for="item in allPresetList"
:key="item.presetId"
:label="item.presetName"
:label="item.presetName ? item.presetName : item.presetId"
:value="item"
/>
</el-select>

View File

@ -12,7 +12,7 @@ export default {
name: 'RtcPlayer',
props: {
videoUrl: { type: String, default: '' },
error: { type: String, default: '' },
error: { default: '' },
hasaudio: { type: Boolean, default: false },
showControls: { type: Boolean, default: true }
},
@ -21,28 +21,16 @@ export default {
timer: null
}
},
watch: {
videoUrl(newData, oldData) {
this.pause()
this.play(newData)
},
immediate: true
},
mounted() {
const paramUrl = decodeURIComponent(this.$route.params.url)
this.$nextTick(() => {
if (typeof (this.videoUrl) === 'undefined') {
this.videoUrl = paramUrl
}
console.log('初始化时的地址为: ' + this.videoUrl)
this.play(this.videoUrl)
})
},
mounted() {},
destroyed() {
clearTimeout(this.timer)
},
methods: {
play: function(url) {
if (webrtcPlayer != null) {
this.pause()
}
webrtcPlayer = new ZLMRTCClient.Endpoint({
element: document.getElementById('webRtcPlayerBox'), // video
debug: true, //
@ -87,6 +75,9 @@ export default {
webrtcPlayer = null
}
},
stop: function() {
this.pause()
},
eventcallbacK: function(type, message) {
console.log('player 事件回调')
console.log(type)

View File

@ -0,0 +1,54 @@
<template>
<div style="width: 100vw; height: 100vh; background-color: #000; overflow: hidden;">
<jessibucaPlayer
v-if="playerType === 0"
ref="player"
:show-button="true"
style="width: 100%; height: 100%"
/>
<rtc-player
v-if="playerType === 1"
ref="player"
:show-controls="true"
style="width: 100%; height: 100%"
/>
<h265web
v-if="playerType === 2"
ref="player"
:show-button="true"
style="width: 100%; height: 100%"
/>
</div>
</template>
<script>
import jessibucaPlayer from './jessibuca.vue'
import rtcPlayer from './rtcPlayer.vue'
import h265web from './h265web.vue'
export default {
name: 'SharePlayer',
components: { jessibucaPlayer, rtcPlayer, h265web },
data() {
return {
playerType: 0
}
},
created() {
const type = parseInt(this.$route.query.type)
if (!isNaN(type) && type >= 0 && type <= 2) {
this.playerType = type
}
},
mounted() {
const url = this.$route.query.url
if (url) {
this.$nextTick(() => {
if (this.$refs.player) {
this.$refs.player.play(decodeURIComponent(url))
}
})
}
}
}
</script>

View File

@ -71,7 +71,6 @@
<rtcPlayer
v-if="activePlayer === 'webRTC'"
ref="recordVideoPlayer"
:video-url="videoUrl"
:has-audio="true"
:show-controls="false"
style="height: calc(100vh - 220px)"
@ -82,7 +81,6 @@
<h265web
v-if="activePlayer === 'h265web'"
ref="recordVideoPlayer"
:video-url="videoUrl"
:height="'calc(100vh - 220px)'"
:show-button="false"
:has-audio="true"

View File

@ -41,7 +41,6 @@
v-if="activePlayer === 'webRTC'"
ref="webRTC"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
@ -55,7 +54,6 @@
<h265web
v-if="activePlayer === 'h265web'"
ref="h265web"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
@ -397,9 +395,12 @@ export default {
},
computed: {
getPlayerShared: function() {
const typeMap = { jessibuca: 0, webRTC: 1, h265web: 2 }
const type = typeMap[this.activePlayer] || 0
const baseUrl = window.location.origin + '/#/play/share?type=' + type + '&url=' + encodeURIComponent(this.videoUrl)
return {
sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
sharedIframe: '<iframe src="' + window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl) + '"></iframe>',
sharedUrl: baseUrl,
sharedIframe: '<iframe src="' + baseUrl + '"></iframe>',
sharedRtmp: this.videoUrl
}
}

View File

@ -52,9 +52,29 @@
<div class="el-icon-loading" />
<div style="width: 100%; line-height: 2rem">正在加载</div>
</div>
<h265web
<jessibucaPlayer
v-if="activePlayer === 'jessibuca'"
ref="recordVideoPlayer"
:has-audio="true"
:height="'calc(100vh - 250px)'"
:show-button="false"
autoplay
@playStatusChange="playingChange"
@playTimeChange="showPlayTimeChange"
/>
<rtcPlayer
v-if="activePlayer === 'webRTC'"
ref="recordVideoPlayer"
:has-audio="true"
:show-controls="false"
style="height: calc(100vh - 250px)"
autoplay
@playStatusChange="playingChange"
@playTimeChange="showPlayTimeChange"
/>
<h265web
v-if="activePlayer === 'h265web'"
ref="recordVideoPlayer"
:video-url="videoUrl"
:height="'calc(100vh - 250px)'"
:show-button="false"
:has-audio="true"
@ -176,6 +196,18 @@
</div>
<div style="text-align: right;">
<div class="record-play-control" style="background-color: transparent; box-shadow: 0 0 10px transparent">
<el-dropdown @command="changePlayer">
<a
target="_blank"
class="record-play-control-item record-play-control-speed"
title="切换播放器"
>{{ playerLabel }}</a>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="jessibuca">Jessibuca</el-dropdown-item>
<el-dropdown-item command="webRTC">WebRTC</el-dropdown-item>
<el-dropdown-item command="h265web">H265Web</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<a
v-if="!isFullScreen"
target="_blank"
@ -203,6 +235,8 @@
<script>
import h265web from '../../common/h265web.vue'
import jessibucaPlayer from '../../common/jessibuca.vue'
import rtcPlayer from '../../common/rtcPlayer.vue'
import VideoTimeline from '../../common/VideoTimeLine/index.vue'
import recordDownload from '../../dialog/recordDownload.vue'
import ChooseTimeRange from '../../dialog/chooseTimeRange.vue'
@ -212,7 +246,7 @@ import screenfull from 'screenfull'
export default {
name: 'DeviceRecord',
components: {
h265web, VideoTimeline, recordDownload, ChooseTimeRange
h265web, jessibucaPlayer, rtcPlayer, VideoTimeline, recordDownload, ChooseTimeRange
},
data() {
return {
@ -225,6 +259,7 @@ export default {
detailFiles: [],
videoUrl: null,
streamInfo: null,
streamId: '',
loading: false,
chooseDate: null,
playTime: null,
@ -246,6 +281,12 @@ export default {
timelineControl: false,
showOtherSpeed: true,
timeSegments: [],
activePlayer: 'jessibuca',
playerUrls: {
jessibuca: ['ws_flv', 'wss_flv'],
webRTC: ['rtc', 'rtcs'],
h265web: ['ws_flv', 'wss_flv']
},
pickerOptions: {
cellClassName: (date) => {
//
@ -260,6 +301,10 @@ export default {
}
},
computed: {
playerLabel() {
const labels = { jessibuca: 'Jessibuca', webRTC: 'WebRTC', h265web: 'H265Web' }
return labels[this.activePlayer] || 'Jessibuca'
},
boxStyle() {
if (this.showSidebar) {
return {
@ -293,6 +338,18 @@ export default {
window.removeEventListener('beforeunload', this.stopPlayRecord)
},
methods: {
changePlayer(player) {
if (this.activePlayer === player) return
this.activePlayer = player
if (this.streamInfo) {
this.videoUrl = this.getUrlByStreamInfo()
this.$nextTick(() => {
if (this.$refs.recordVideoPlayer) {
this.$refs.recordVideoPlayer.play(this.videoUrl)
}
})
}
},
sidebarControl() {
this.showSidebar = !this.showSidebar
},
@ -438,16 +495,23 @@ export default {
})
.then((data) => {
this.streamInfo = data
this.streamId = data.stream
this.videoUrl = this.getUrlByStreamInfo()
this.hasAudio = this.streamInfo.tracks && this.streamInfo.tracks.length > 1
this.$nextTick(() => {
if (this.$refs.recordVideoPlayer) {
this.$refs.recordVideoPlayer.play(this.videoUrl)
}
})
})
}
},
getUrlByStreamInfo() {
const keys = this.playerUrls[this.activePlayer]
if (location.protocol === 'https:') {
this.videoUrl = this.streamInfo['wss_flv']
this.videoUrl = this.streamInfo[keys[1]]
} else {
this.videoUrl = this.streamInfo['ws_flv']
this.videoUrl = this.streamInfo[keys[0]]
}
return this.videoUrl
},

View File

@ -20,10 +20,10 @@
>
<el-tab-pane label="Jessibuca" name="jessibuca">
<jessibucaPlayer
style="min-height: 22.5vw"
v-if="activePlayer === 'jessibuca'"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
@ -37,7 +37,6 @@
v-if="activePlayer === 'webRTC'"
ref="webRTC"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
@ -51,7 +50,6 @@
<h265web
v-if="activePlayer === 'h265web'"
ref="h265web"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
@ -63,44 +61,6 @@
</el-tab-pane>
</el-tabs>
<jessibucaPlayer
v-if="Object.keys(this.player).length == 1 && this.player.jessibuca"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
:has-audio="hasAudio"
fluent
autoplay
live
/>
<rtc-player
v-if="Object.keys(this.player).length == 1 && this.player.webRTC"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
:has-audio="hasAudio"
fluent
autoplay
live
/>
<h265web
v-if="Object.keys(this.player).length == 1 && this.player.h265web"
ref="jessibuca"
:visible.sync="showVideoDialog"
:video-url="videoUrl"
:error="videoError"
:message="videoError"
height="100px"
:has-audio="hasAudio"
fluent
autoplay
live
/>
</div>
<div id="shared" style="text-align: right; margin-top: 1rem;">
@ -354,6 +314,7 @@
<script>
import rtcPlayer from '../../common/rtcPlayer.vue'
import elDragDialog from '@/directive/el-drag-dialog'
import crypto from 'crypto'
import jessibucaPlayer from '../../common/jessibuca.vue'
import mediaInfo from '../../common/mediaInfo.vue'
@ -361,6 +322,7 @@ import H265web from '../../common/h265web.vue'
export default {
name: 'DevicePlayer',
directives: { elDragDialog },
components: {
mediaInfo, H265web,
jessibucaPlayer, rtcPlayer
@ -414,9 +376,12 @@ export default {
},
computed: {
getPlayerShared: function() {
const typeMap = { jessibuca: 0, webRTC: 1, h265web: 2 }
const type = typeMap[this.activePlayer] || 0
const baseUrl = window.location.origin + '/#/play/share?type=' + type + '&url=' + encodeURIComponent(this.videoUrl)
return {
sharedUrl: window.location.origin + '/#/play/wasm/' + encodeURIComponent(this.videoUrl),
sharedIframe: '' + window.location.origin + '<iframe src="/public#/play/wasm/"></iframe>' + encodeURIComponent(this.videoUrl) + '',
sharedUrl: baseUrl,
sharedIframe: '<iframe src="' + baseUrl + '"></iframe>',
sharedRtmp: this.videoUrl
}
}
@ -440,6 +405,13 @@ export default {
changePlayer: function(tab) {
this.activePlayer = tab.name
this.videoUrl = this.getUrlByStreamInfo()
if (this.$refs[this.activePlayer]) {
this.$refs[this.activePlayer].play(this.videoUrl)
} else {
this.$nextTick(() => {
this.$refs[this.activePlayer].play(this.videoUrl)
})
}
},
openDialog: function(tab, deviceId, channelId, param) {
if (this.showVideoDialog) {

View File

@ -33,8 +33,7 @@
<div v-if="!videoUrl[i-1]" class="no-signal">{{ videoTip[i-1]?videoTip[i-1]:"无信号" }}</div>
<player
v-else
:ref="'player'[i-1]"
:video-url="videoUrl[i-1]"
:ref="'player' + i"
fluent
autoplay
:show-button="true"
@ -220,6 +219,15 @@ export default {
setTimeout(() => {
window.localStorage.setItem('videoUrl', JSON.stringify(_this.videoUrl))
}, 100)
this.$nextTick(() => {
const refName = 'player' + (idx + 1)
if (this.$refs[refName]) {
const player = this.$refs[refName] instanceof Array ? this.$refs[refName][0] : this.$refs[refName]
if (player && player.play) {
player.play(url)
}
}
})
},
checkPlayByParam() {
const query = this.$route.query

View File

@ -87,8 +87,8 @@
<el-table-column prop="stream" label="流ID" min-width="200" />
<el-table-column label="推流状态" min-width="100">
<template v-slot:default="scope">
<el-tag v-if="scope.row.pushing && $myServerId !== scope.row.serverId" size="medium" style="border-color: #ecf1af">推流中</el-tag>
<el-tag v-if="scope.row.pushing && $myServerId === scope.row.serverId" size="medium">推流中</el-tag>
<el-tag v-if="scope.row.pushing && myServerId !== scope.row.serverId" size="medium" style="border-color: #ecf1af">推流中</el-tag>
<el-tag v-if="scope.row.pushing && myServerId === scope.row.serverId" size="medium">推流中</el-tag>
<el-tag v-if="!scope.row.pushing" size="medium" type="info">已停止</el-tag>
</template>
</el-table-column>
@ -189,6 +189,11 @@ export default {
destroyed() {
clearTimeout(this.updateLooper)
},
computed: {
myServerId() {
return this.$store.getters.serverId
}
},
methods: {
initData: function() {
this.loading = true

View File

@ -107,7 +107,7 @@ create table IF NOT EXISTS wvp_mobile_position
(
id serial primary key,
channel_id character varying(50) not null,
timestamp int8 varying(50),
timestamp int8,
longitude double precision,
latitude double precision,
altitude double precision,
@ -115,16 +115,16 @@ create table IF NOT EXISTS wvp_mobile_position
direction double precision,
create_time character varying(50)
);
COMMENT ON TABLE wvp_device_mobile_position IS '存储移动位置订阅上报的数据';
COMMENT ON COLUMN wvp_device_mobile_position.id IS '主键ID';
COMMENT ON COLUMN wvp_device_mobile_position.channel_id IS '通道ID';
COMMENT ON COLUMN wvp_device_mobile_position.timestamp IS '上报时间';
COMMENT ON COLUMN wvp_device_mobile_position.longitude IS '经度';
COMMENT ON COLUMN wvp_device_mobile_position.latitude IS '纬度';
COMMENT ON COLUMN wvp_device_mobile_position.altitude IS '海拔';
COMMENT ON COLUMN wvp_device_mobile_position.speed IS '速度';
COMMENT ON COLUMN wvp_device_mobile_position.direction IS '方向角';
COMMENT ON COLUMN wvp_device_mobile_position.create_time IS '入库时间';
COMMENT ON TABLE wvp_mobile_position IS '存储移动位置订阅上报的数据';
COMMENT ON COLUMN wvp_mobile_position.id IS '主键ID';
COMMENT ON COLUMN wvp_mobile_position.channel_id IS '通道ID';
COMMENT ON COLUMN wvp_mobile_position.timestamp IS '上报时间';
COMMENT ON COLUMN wvp_mobile_position.longitude IS '经度';
COMMENT ON COLUMN wvp_mobile_position.latitude IS '纬度';
COMMENT ON COLUMN wvp_mobile_position.altitude IS '海拔';
COMMENT ON COLUMN wvp_mobile_position.speed IS '速度';
COMMENT ON COLUMN wvp_mobile_position.direction IS '方向角';
COMMENT ON COLUMN wvp_mobile_position.create_time IS '入库时间';
drop table IF EXISTS wvp_device_channel;

View File

@ -41,7 +41,7 @@ ALTER table wvp_device_channel ADD COLUMN IF NOT EXISTS map_level integer defaul
ALTER table wvp_common_group ADD COLUMN IF NOT EXISTS alias varchar(255) default null;
ALTER table wvp_stream_proxy DROP COLUMN IF EXISTS enable_remove_none_reader;
drop index uk_media_server_unique_ip_http_port on wvp_media_server;
DROP INDEX IF EXISTS uk_media_server_unique_ip_http_port;
ALTER table wvp_device DROP COLUMN IF EXISTS register_time;
ALTER table wvp_device DROP COLUMN IF EXISTS keepalive_time;
@ -56,7 +56,7 @@ create table IF NOT EXISTS wvp_alarm (
latitude double precision,
alarm_type integer,
alarm_time bigint
)
);
COMMENT ON COLUMN wvp_alarm.id IS '主键ID';
COMMENT ON COLUMN wvp_alarm.channel_id IS '关联通道的数据库id';
COMMENT ON COLUMN wvp_alarm.description IS '报警描述';