物联网视频监控服务(四)-监控客户端篇

该文章介绍了一个使用Go语言和FyneGUI框架开发的视频客户端,它支持开启或关闭实时视频监控,根据输入的时间段下载视频文件。客户端通过UDP和TCP协议进行通信,利用HTTP协议处理文件下载。用户界面包括视频播放、直播控制、时间输入和文件选择功能。
摘要由CSDN通过智能技术生成

概述

此篇文章主要描述 监控客户端(video_client) 开发部分;

功能点

  • 开启或关闭实时视频监控功能
  • 根据输入的开始结束时间下载视频文件到指定文件夹

使用技术

  • 语言:go
  • 软件或框架:fyne(go语言的GUI框架)
  • 应用层协议:HTTP
  • 传输层协议:UDP,TCP

系统设计

此服务数据流概览

在这里插入图片描述

知识准备

  • fyne: 用go语言写的一个GUI框架,支持跨平台;

实现部分

UI效果图

在这里插入图片描述

代码部分

main.go

package main

import (
	"fmt"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/canvas"
	"fyne.io/fyne/v2/container"
	"github.com/flopp/go-findfont"
	"github.com/goki/freetype/truetype"
	"os"
	"time"
	"video_client/gui_service"
	"video_client/udp_client"
)

//
//  @Description: main函数的defer操作,负责 udp服务关闭 及 通知udp服务端 停止发送视频帧 的信号
//  @param MqttMessageChannel:
//
func MainDefer(MqttMessageChannel chan []byte) {
	defer udp_client.UDPClose()
	defer func() {
		fmt.Println("程序退出前,发送停止直播信号")
		//	连接udp服务,接收视频帧数据,并播放
		MqttMessageChannel <- []byte("clientStop")
		time.Sleep(1 * time.Second)
	}()
}

//
//  @Description: 初始化,功能为 让fyne框架支持中文显示
//
func init() {
	fontPath, err := findfont.Find("Songti.ttc")
	if err != nil {
		panic(err)
	}
	fmt.Printf("Found 'arial.ttf' in '%s'\n", fontPath)

	// load the font with the freetype library
	// 原作者使用的ioutil.ReadFile已经弃用
	fontData, err := os.ReadFile(fontPath)
	if err != nil {
		panic(err)
	}
	_, err = truetype.Parse(fontData)
	if err != nil {
		panic(err)
	}
	_ = os.Setenv("FYNE_FONT", fontPath)
}

//
//  @Description: UDP相关配置运行
//  @param MqttMessageChannel:
//  @param img:
//
func SetUp(MqttMessageChannel chan []byte, img *canvas.Image) {
	//udp客户端启动并接收数据
	udp_client.UDPClientSetUp()
	go udp_client.UDPSend(MqttMessageChannel)
	go udp_client.UDPReceive(img)
}

//
//  @Description:
//
func main() {
	//GUI初始化
	myApp := app.New()
	myWindow := myApp.NewWindow("Video Client")

	//第一行 视频播放窗口
	img, one := gui_service.FirstLineButton()

	//第二行 直播/停止直播 按钮
	MqttMessageChannel := make(chan []byte, 20)
	SetUp(MqttMessageChannel, img)
	defer MainDefer(MqttMessageChannel)
	two := gui_service.SecondLineButton(MqttMessageChannel)

	//第三行 输入框 和 筛选按钮 放在一个子 水平布局中
	startTimeEntry, endTimeEntry, three := gui_service.ThirdLineButton()

	//第四行,功能是 选择文件夹及下载按钮
	four := gui_service.FourthLineButton(myWindow, startTimeEntry, endTimeEntry)

	//将四行放到一个window中
	all := container.NewVBox(one, two, three, four)
	myWindow.SetContent(all)

	//fyne gui框架运行及展示
	myWindow.ShowAndRun()
}

gui.go

//
// All rights reserved
// create time '2023/2/14 16:48'
//
// Usage:
//

package gui_service

import (
	"encoding/json"
	"fmt"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/canvas"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/layout"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
	"github.com/asmcos/requests"
	"video_client/utils/constant"
	"video_client/utils/gtime"
	"video_client/utils/requests_handler"
)

//
//  @Description: 第一行 视频播放窗口
//  @return *canvas.Image:
//  @return *fyne.Container:
//
func FirstLineButton() (*canvas.Image, *fyne.Container) {
	img := canvas.NewImageFromResource(theme.FyneLogo())
	img.FillMode = canvas.ImageFillOriginal
	//first := container.NewGridWithColumns(1, img)
	first := container.New(
		layout.NewGridWrapLayout(fyne.NewSize(500, 500)), img)
	return img, first
}

//
//  @Description: 第二行 直播/停止直播按钮及操作
//  @param MqttMessageChannel:
//  @param img:
//  @return *fyne.Container:
//
func SecondLineButton(MqttMessageChannel chan []byte) *fyne.Container {
	//第二行 监控指标按钮
	playButton := widget.NewButton("开始直播", func() {
		fmt.Println("点击直播按钮了")
		//	连接udp服务,接收视频帧数据,并播放
		MqttMessageChannel <- []byte("clientPlay")
	})
	playButton.Icon = theme.ConfirmIcon()
	stopButton := widget.NewButton("停止直播", func() {
		fmt.Println("点击停止直播按钮了")
		//	连接udp服务,接收视频帧数据,并播放
		MqttMessageChannel <- []byte("clientStop")
	})
	stopButton.Icon = theme.CancelIcon()
	return container.NewGridWithColumns(2, playButton, stopButton)
}

//
//  @Description: 第三行 输入框 和 筛选按钮及相关操作函数
//  @return *widget.Entry:
//  @return *widget.Entry:
//  @return *fyne.Container:
//
func ThirdLineButton() (*widget.Entry, *widget.Entry, *fyne.Container) {
	startTimeEntry := widget.NewEntry()
	startTimeEntry.SetPlaceHolder("输入开始时间")
	startTimeEntry.SetText(gtime.GetCurrentTime())
	endTimeEntry := widget.NewEntry()
	endTimeEntry.SetPlaceHolder("输入结束时间")
	three := container.NewGridWithColumns(2, startTimeEntry, endTimeEntry)
	return startTimeEntry, endTimeEntry, three
}

//
//  @Description: 第四行 选择文件夹及下载按钮 和对应函数操作
//  @param myWindow:
//  @param startTimeEntry:
//  @param endTimeEntry:
//  @return *fyne.Container:
//
func FourthLineButton(myWindow fyne.Window, startTimeEntry *widget.Entry, endTimeEntry *widget.Entry) *fyne.Container {
	var DownloadDir string
	HistoryVideoDownloadFunc := func() {
		defer RecoverAllPanic(myWindow)
		//根据开始/结束时间 查看时间范围内的 文件名列表
		resp, err := requests.PostJson(fmt.Sprintf("%v/video/query", constant.VideoServer), map[string]interface{}{"start_time": startTimeEntry.Text, "end_time": endTimeEntry.Text})
		if err != nil {
			dialog.ShowInformation("request error", fmt.Sprintf("%v", err), myWindow)
			return
		}

		respData, err := requests_handler.ResponseCheck(resp)
		if err != nil {
			dialog.ShowInformation("response check error", fmt.Sprintf("%v", err), myWindow)
			return
		}

		if respData["result"] != nil {
			files := respData["result"].([]interface{})
			//	开始下载
			for _, file := range files {
				x, err := json.Marshal(map[string]interface{}{"FileName": file})
				if err != nil {
					dialog.ShowInformation("json marshal error", fmt.Sprintf("%v", err), myWindow)
					return
				}
				//下载文件
				resp, err := requests.Get(fmt.Sprintf("%v/video/download?FileName=%v", constant.VideoServer, file), x)
				if err != nil {
					dialog.ShowInformation("download status error", fmt.Sprintf("%v", err), myWindow)
					return
				}
				err = resp.SaveFile(fmt.Sprintf("%v/%v", DownloadDir, file))
				if err != nil {
					dialog.ShowInformation("download error", fmt.Sprintf("%v", err), myWindow)
					return
				}
			}
		}
		dialog.ShowInformation("下载成功", fmt.Sprintf("请在 %v 文件夹中查看下载的文件", DownloadDir), myWindow)
	}

	filterButton := widget.NewButton("历史视频下载", HistoryVideoDownloadFunc)
	filterButton.Icon = theme.ConfirmIcon()

	DirButton := widget.NewButton("选择下载文件夹", func() {
		dialog.ShowFolderOpen(func(closer fyne.ListableURI, err error) {
			if err != nil {
				dialog.ShowInformation("open dir error", fmt.Sprintf("%v", err), myWindow)
				return
			}
			if closer != nil {
				DownloadDir = closer.Path()
				dialog.ShowInformation("下载文件夹选择成功", fmt.Sprintf("%v", DownloadDir), myWindow)
			}
		}, myWindow)
	})
	filterButton.Icon = theme.ConfirmIcon()

	return container.NewGridWithColumns(2, DirButton, filterButton)
}

udp.go

// 
// All rights reserved
// create time '2022/12/9 16:56'
//
// Usage:
//

package udp_client

import (
	"fmt"
	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/canvas"
	"net"
	"video_client/utils/constant"
)

var UDPListen *net.UDPConn

//
//  @Description: 启动udp客户端
//
func UDPClientSetUp() {
	var err error
	UDPListen, err = net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   constant.UdpServerIp,
		Port: constant.UdpServerPort,
	})
	if err != nil {
		panic(fmt.Sprintf("监听UDP服务失败,请修改,err:", err))
	}
	fmt.Println(fmt.Sprintf("连接UDP服务成功,端口为:%v", constant.UdpServerPort))
	return
}

//
//  @Description: 收尾性工作;关闭udp client
//
func UDPClose() {
	err := UDPListen.Close()
	if err != nil {
		panic(fmt.Sprintf("关闭UDPListen失败,err:%v", err))
	}
}

//
//  @Description: 接收UDP服务端的图片数据,并展示给gui(直播使用)
//  @param Img:
//
func UDPReceive(Img *canvas.Image) {
	for {
		//定义一个长度为10万的切片
		sliceData := make([]byte, 100000, 100000)
		//将读取的数据存到数组中
		n, err := UDPListen.Read(sliceData)
		if err != nil {
			fmt.Println("读取UDP数据失败,err:", err)
			continue
		}
		if n == 0 {
			continue
		}
		//将读取的视频帧数据 以图片方式展示出来
		res := &fyne.StaticResource{
			StaticName:    "test",
			StaticContent: sliceData,
		}
		Img.Resource = res
		Img.Refresh()
	}
}

//
//  @Description: 接收channel中的message 信号数据,并发送数据给服务端
//  @param MqttMessageChannel:
//
func UDPSend(MqttMessageChannel <-chan []byte) {
	for {
		message, ok := <-MqttMessageChannel
		if !ok {
			fmt.Println("发送数据到图片处理channel关闭")
			break
		}
		b, err := UDPListen.Write(message)
		if err != nil {
			panic(fmt.Sprintf("发送UDP数据失败 err:%v", err))
		}
		fmt.Println("发送数据 %v", b)
	}
}

相关链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值