git commit -m "first commit"

master
朱瑶 2 weeks ago
commit c979a97b15

5
.gitignore vendored

@ -0,0 +1,5 @@
target/
*.log
*.class
.idea
.vscode

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>camera-front</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,23 @@
{
"name": "camera-front",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.2",
"axios": "^1.13.6",
"element-plus": "^2.13.5",
"flv.js": "^1.6.2",
"vue": "^3.5.25",
"vue-router": "^4.6.4"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.2",
"vite": "^7.3.1"
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@ -0,0 +1,7 @@
<template>
<router-view />
</template>
<script setup>
//
</script>

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

@ -0,0 +1,43 @@
<script setup>
import { ref } from 'vue'
defineProps({
msg: String,
})
const count = ref(0)
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Learn more about IDE Support for Vue in the
<a
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
target="_blank"
>Vue Docs Scaling up Guide</a
>.
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>

@ -0,0 +1,27 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import App from './App.vue'
import CameraView from './views/CameraView.vue'
import { createRouter, createWebHistory } from 'vue-router'
// 路由配置
const routes = [
{ path: '/', component: CameraView }
]
const router = createRouter({
history: createWebHistory(),
routes
})
const app = createApp(App)
app.use(ElementPlus)
app.use(router)
// 2. 全局注册所有图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.mount('#app')

@ -0,0 +1,79 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

@ -0,0 +1,275 @@
<template>
<div class="camera-container">
<el-card class="camera-card">
<template #header>
<div class="card-header">
<span>摄像头监控</span>
<el-button type="primary" @click="getFlvUrl"></el-button>
</div>
</template>
<!-- 视频播放区域 -->
<div class="video-container">
<video id="videoElement" ref="videoRef" controls autoplay muted width="100%" height="auto"></video>
</div>
<!-- 设备参数配置 -->
<div class="form-container">
<el-form :inline="true" :model="formData" class="demo-form-inline">
<el-form-item label="设备17位码">
<el-input v-model="formData.qrCode" placeholder="输入设备17位码"></el-input>
</el-form-item>
<el-form-item label="MAC地址">
<el-input v-model="formData.mac" placeholder="格式XX-XX-XX-XX-XX-XX"></el-input>
</el-form-item>
<el-form-item label="通道ID">
<el-input-number v-model="formData.channelId" :min="1" :max="32" value="1"></el-input-number>
</el-form-item>
</el-form>
</div>
<!-- 云台控制区域 -->
<div class="ptz-control">
<h3>云台控制</h3>
<div class="ctr-durtion">
<div @mousedown="() => controlPTZ(7)" @mouseup="stopPTZ"></div>
<div @mousedown="() => controlPTZ(3)" @mouseup="stopPTZ"></div>
<div @mousedown="() => controlPTZ(5)" @mouseup="stopPTZ"></div>
<div @mousedown="() => controlPTZ(1)" @mouseup="stopPTZ"></div>
</div>
<!-- 圆形云台控制器 -->
<!-- 变倍/调焦/复位控制区 -->
<!-- <div class="ptz-extra-controls">-->
<!-- <el-button @click="() => controlPTZ(10)" icon="zoom-in" circle>变倍+</el-button>-->
<!-- <el-button @click="() => controlPTZ(11)" icon="zoom-out" circle>变倍-</el-button>-->
<!-- <el-button @click="() => controlPTZ(12)" circle>调焦+</el-button>-->
<!-- <el-button @click="() => controlPTZ(13)" circle>调焦-</el-button>-->
<!-- <el-button @click="() => controlPTZ(15)" type="warning" circle>云台复位</el-button>-->
<!-- </div>-->
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import axios from 'axios'
import flvjs from 'flv.js'
//
const formData = ref({
qrCode: '81AF77AA54015EE9B', //
mac: 'F8-CE-21-A8-BB-4B',
channelId: 1
})
//
let flvPlayer = null
const videoRef = ref(null)
// FLV
const getFlvUrl = async () => {
try {
const res = await axios.post('http://localhost:8080/api/camera/flv-url', null, {
params: {
qrCode: formData.value.qrCode,
mac: formData.value.mac,
channelId: formData.value.channelId
}
})
if (res.data.code === 200) {
console.log(res)
const data = res.data.data
if (data.url) {
initFlvPlayer(data.url)
ElMessage.success('视频加载成功')
} else {
ElMessage.error('未获取到播放地址:' + data.msg)
}
} else {
ElMessage.error(res.data.msg)
}
} catch (error) {
ElMessage.error('请求失败:' + error.message)
}
}
// FLV
const initFlvPlayer = (url) => {
//
if (flvPlayer) {
flvPlayer.destroy()
flvPlayer = null
}
if (flvjs.isSupported()) {
flvPlayer = flvjs.createPlayer({
type: 'flv',
url: url,
isLive: true, //
hasAudio: false //
})
flvPlayer.attachMediaElement(videoRef.value)
flvPlayer.load()
flvPlayer.play()
} else {
ElMessage.warning('当前浏览器不支持FLV播放')
}
}
//
const controlPTZ = async (direction) => {
try {
const res = await axios.post('http://localhost:8080/api/camera/ptz-control', null, {
params: {
qrCode: formData.value.qrCode,
mac: formData.value.mac,
channelId: formData.value.channelId,
direction: direction,
speed: 3 // 3
}
})
if (res.data.code !== 200) {
ElMessage.error(res.data.msg)
}
} catch (error) {
ElMessage.error('云台控制失败:' + error.message)
}
}
//
const stopPTZ = () => {
controlPTZ(0)
}
//
onUnmounted(() => {
if (flvPlayer) {
flvPlayer.destroy()
}
})
</script>
<style scoped>
.camera-container {
padding: 20px;
}
.camera-card {
max-width: 1200px;
margin: 0 auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.video-container {
margin: 20px 0;
border: 1px solid #e6e6e6;
border-radius: 4px;
padding: 10px;
}
.form-container {
margin: 20px 0;
}
/* 整体控制区样式 */
.ptz-control {
margin-top: 30px;
display: flex;
flex-direction: column;
align-items: center;
}
.ptz-control h3 {
margin-bottom: 20px;
font-size: 16px;
color: #303133;
}
/* 圆形摇杆容器 */
.ptz-joystick {
position: relative;
width: 220px;
height: 220px;
border-radius: 50%;
background-color: #f5f7fa;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 20px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.ptz-extra-controls .el-button {
width: 80px;
height: 40px;
}
.ctr-durtion {
width: 10em;
height: 10em;
border-radius: 10em;
overflow: hidden;
position: relative;
box-sizing: border-box;
backdrop-filter: blur(5px);
user-select: none;
/* 禁止选中文本 */
-webkit-user-select: none;
touch-action: none;
/* 禁止默认触摸行为 */
* {
box-sizing: border-box;
}
&>div {
width: 10em;
height: 10em;
position: absolute;
left: 5em;
top: 5em;
cursor: pointer;
background-color: var(--ctr-durtion-bg, #0001);
transform-origin: 0 0;
transform: rotate(45deg);
&::before {
content: '';
width: 0;
height: 0;
border: solid #0000 1em;
border-left-color: var(--ctr-durtion-arrow, #FFFC);
position: absolute;
left: 1.5em;
top: 1.5em;
transform: rotate(45deg);
}
&:hover,
&.hover {
background-color: var(--ctr-durtion-hover, #0002);
}
&:active,
&.active {
background-color: var(--ctr-durtion-active, #0003);
}
&:nth-child(2) {
transform: rotate(135deg);
}
&:nth-child(3) {
transform: rotate(225deg);
}
&:nth-child(4) {
transform: rotate(-45deg);
}
}
}
</style>

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
})

@ -0,0 +1,73 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>camera-control</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>camera-control</name>
<description>Camera control Spring Boot project</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- OkHttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- FastJSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.43</version>
</dependency>
<!-- 跨域 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- 配置读取 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -0,0 +1,12 @@
package com.example.cameracontrol;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CameraControlApplication {
public static void main(String[] args) {
SpringApplication.run(CameraControlApplication.class, args);
}
}

@ -0,0 +1,64 @@
package com.example.cameracontrol.controller;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.cameracontrol.util.TplinkApiUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/camera")
@CrossOrigin(origins = "*") // 允许跨域(生产环境需限制具体域名)
public class CameraController {
@Autowired
private TplinkApiUtil tplinkApiUtil;
// 获取摄像头FLV播放地址
@PostMapping("/flv-url")
public Map<String, Object> getFlvUrl(
@RequestParam(required = false) String qrCode,
@RequestParam(required = false) String mac,
@RequestParam(defaultValue = "1") int channelId
) {
Map<String, Object> result = new HashMap<>();
try {
String response = tplinkApiUtil.getFlvUrl(qrCode, mac, channelId);
JSONObject object = JSON.parseObject(response);
result.put("code", 200);
result.put("data", object.get("result"));
result.put("msg", "获取播放地址成功");
} catch (Exception e) {
result.put("code", 500);
result.put("msg", "获取播放地址失败:" + e.getMessage());
}
return result;
}
// 云台控制
@PostMapping("/ptz-control")
public Map<String, Object> controlPTZ(
@RequestParam(required = false) String qrCode,
@RequestParam(required = false) String mac,
@RequestParam(defaultValue = "1") int channelId,
@RequestParam int direction,
@RequestParam(required = false) Integer speed
) {
Map<String, Object> result = new HashMap<>();
try {
String response = tplinkApiUtil.controlPTZ(qrCode, mac, channelId, direction, speed);
result.put("code", 200);
result.put("data", response);
result.put("msg", "云台控制成功");
} catch (Exception e) {
result.put("code", 500);
result.put("msg", "云台控制失败:" + e.getMessage());
}
return result;
}
}

@ -0,0 +1,144 @@
package com.example.cameracontrol.util;
import com.alibaba.fastjson.JSONObject;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Component
public class TplinkApiUtil {
@Value("${tplink.ak}")
private String AK;
@Value("${tplink.sk}")
private String SK;
@Value("${tplink.host}")
private String HOST;
@Value("${tplink.flv-url-path}")
private String FLV_PATH;
@Value("${tplink.ptz-control-path}")
private String PTZ_PATH;
private static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
private static final String TERMINAL_ID = UUID.randomUUID().toString().replaceAll("-", "");
// HmacSHA256 签名
private byte[] hmac256(byte[] key, String msg) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm());
mac.init(secretKeySpec);
return mac.doFinal(msg.getBytes(StandardCharsets.UTF_8));
}
// SHA256 转小写十六进制
private String sha256Hex(String s) throws Exception {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(s.getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printHexBinary(digest).toLowerCase();
}
// 生成Authorization签名
private String generateAuthorization(String payload, String path) throws Exception {
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonce = UUID.randomUUID().toString().replaceAll("-", "");
String algorithm = "HmacSHA256";
String hashedPayload = sha256Hex(payload);
String credentialScope = "POST " + path + " tp-link_request";
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedPayload;
byte[] secretDate = hmac256(SK.getBytes(StandardCharsets.UTF_8), timestamp);
byte[] secretService = hmac256(secretDate, path);
byte[] secretSigning = hmac256(secretService, "tp-link");
String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase();
return "Timestamp=" + timestamp + "," +
"Nonce=" + nonce + "," +
"AccessKey=" + AK + "," +
"Signature=" + signature + "," +
"TerminalId=" + TERMINAL_ID;
}
// 发送POST请求
private String sendPostRequest(String path, String payload) throws Exception {
String authorization = generateAuthorization(payload, path);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
RequestBody requestBody = RequestBody.create(payload, JSON_MEDIA_TYPE);
Request request = new Request.Builder()
.url("https://" + HOST + path)
.post(requestBody)
.addHeader("X-Authorization", authorization)
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Host", HOST)
.build();
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
throw new RuntimeException("请求失败HTTP状态码=" + response.code() + ",信息=" + response.message());
}
return response.body().string();
}
}
// 获取FLV播放地址
public String getFlvUrl(String qrCode, String mac, int channelId) throws Exception {
// 参数校验
if (qrCode == null && mac == null) {
throw new IllegalArgumentException("qrCode和mac必须至少传一个");
}
if (channelId <= 0) {
throw new IllegalArgumentException("channelId必须为正整数");
}
// 构建请求体
JSONObject payloadJson = new JSONObject();
if (qrCode != null) payloadJson.put("qrCode", qrCode);
if (mac != null) payloadJson.put("mac", mac);
payloadJson.put("channelId", channelId);
payloadJson.put("type", "video");
payloadJson.put("resolution", "HD"); // 默认高清
payloadJson.put("videoCode", "H264");
return sendPostRequest(FLV_PATH, payloadJson.toJSONString());
}
// 云台控制
public String controlPTZ(String qrCode, String mac, int channelId, int direction, Integer speed) throws Exception {
// 参数校验
if (qrCode == null && mac == null) {
throw new IllegalArgumentException("qrCode和mac必须至少传一个");
}
if (channelId <= 0) {
throw new IllegalArgumentException("channelId必须为正整数");
}
if (direction < 0 || direction > 15) {
throw new IllegalArgumentException("direction必须是0-15之间的整数");
}
if (speed != null && (speed < 1 || speed > 7)) {
throw new IllegalArgumentException("speed必须是1-7之间的整数");
}
// 构建请求体
JSONObject payloadJson = new JSONObject();
if (qrCode != null) payloadJson.put("qrCode", qrCode);
if (mac != null) payloadJson.put("mac", mac);
payloadJson.put("channelId", channelId);
payloadJson.put("direction", direction);
if (speed != null) payloadJson.put("speed", speed);
return sendPostRequest(PTZ_PATH, payloadJson.toJSONString());
}
}

@ -0,0 +1,11 @@
server:
port: 8080
# TP-LINK 配置
tplink:
ak: f050bd2e75bd4f47be45c841dc895cc8 # 替换为你的AK
sk: 16a747f92d3f4aaba63bfebda3edb8ed # 替换为你的SK
host: api-smbcloud.tp-link.com.cn
# 接口路径
flv-url-path: /vms/open/httpFlvService/v1/getHttpFlvUrl
ptz-control-path: /vms/open/deviceConfig/v1/motionCtrl

@ -0,0 +1,12 @@
package com.example.cameracontrol;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class CameraControlApplicationTests {
@Test
void contextLoads() {
}
}
Loading…
Cancel
Save