From f59883315d11a4671552531f7ee2043471a81646 Mon Sep 17 00:00:00 2001 From: tdv Date: Thu, 16 Oct 2025 18:49:02 +0300 Subject: [PATCH] implemented audio streaming to browser, but it didnt work --- management-ui/package-lock.json | 7 + management-ui/package.json | 1 + .../DeviceCertificateDialog.vue | 151 +++++++--- .../src/customcompometns/DeviceComponent.vue | 285 +++++++++++++----- management-ui/src/types/hlsjs.d.ts | 4 + management-ui/tsconfig.json | 2 +- server/internal/dto/mediamtx.go | 2 +- server/internal/handlers/mediamtx.go | 37 ++- server/internal/router/router.go | 2 +- 9 files changed, 378 insertions(+), 113 deletions(-) create mode 100644 management-ui/src/types/hlsjs.d.ts diff --git a/management-ui/package-lock.json b/management-ui/package-lock.json index ecee315..470c497 100644 --- a/management-ui/package-lock.json +++ b/management-ui/package-lock.json @@ -14,6 +14,7 @@ "axios": "^1.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "hls.js": "^1.6.13", "leaflet": "^1.9.4", "lucide-vue-next": "^0.525.0", "reka-ui": "^2.5.0", @@ -2006,6 +2007,12 @@ "he": "bin/he" } }, + "node_modules/hls.js": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.13.tgz", + "integrity": "sha512-hNEzjZNHf5bFrUNvdS4/1RjIanuJ6szpWNfTaX5I6WfGynWXGT7K/YQLYtemSvFExzeMdgdE4SsyVLJbd5PcZA==", + "license": "Apache-2.0" + }, "node_modules/jiti": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", diff --git a/management-ui/package.json b/management-ui/package.json index aacf039..317b31f 100644 --- a/management-ui/package.json +++ b/management-ui/package.json @@ -15,6 +15,7 @@ "axios": "^1.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "hls.js": "^1.6.13", "leaflet": "^1.9.4", "lucide-vue-next": "^0.525.0", "reka-ui": "^2.5.0", diff --git a/management-ui/src/customcompometns/DeviceCertificateDialog.vue b/management-ui/src/customcompometns/DeviceCertificateDialog.vue index 847681c..a34611e 100644 --- a/management-ui/src/customcompometns/DeviceCertificateDialog.vue +++ b/management-ui/src/customcompometns/DeviceCertificateDialog.vue @@ -1,49 +1,132 @@ - \ No newline at end of file + diff --git a/management-ui/src/customcompometns/DeviceComponent.vue b/management-ui/src/customcompometns/DeviceComponent.vue index 179b961..c8540d0 100644 --- a/management-ui/src/customcompometns/DeviceComponent.vue +++ b/management-ui/src/customcompometns/DeviceComponent.vue @@ -1,87 +1,226 @@ + \ No newline at end of file + + + Dashboard + Records + Livestream + + + + + + + + + + + +
+
+ + +
+ +
+ {{ streamError }} +
+ + +
+ +

+ HLS: {{ hlsUrl }} +

+
+
+
+
+ diff --git a/management-ui/src/types/hlsjs.d.ts b/management-ui/src/types/hlsjs.d.ts new file mode 100644 index 0000000..1766c1b --- /dev/null +++ b/management-ui/src/types/hlsjs.d.ts @@ -0,0 +1,4 @@ +declare module 'hls.js' { + const Hls: any + export default Hls +} \ No newline at end of file diff --git a/management-ui/tsconfig.json b/management-ui/tsconfig.json index 856b926..474a099 100644 --- a/management-ui/tsconfig.json +++ b/management-ui/tsconfig.json @@ -7,7 +7,7 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*","src/types/**/*"] } } } diff --git a/server/internal/dto/mediamtx.go b/server/internal/dto/mediamtx.go index f322c87..e6a7ce7 100644 --- a/server/internal/dto/mediamtx.go +++ b/server/internal/dto/mediamtx.go @@ -32,7 +32,7 @@ type PublishTokenReq struct { } type PublishTokenResp struct { - WHIP string `json:"whipUrl"` // http://mediamtx:8889/whip/live/?token=... + HLS string `json:"hlsUrl"` // https:///hls/live//index.m3u8?token=... } type ReadTokenReq struct { diff --git a/server/internal/handlers/mediamtx.go b/server/internal/handlers/mediamtx.go index 00eae30..0e96992 100644 --- a/server/internal/handlers/mediamtx.go +++ b/server/internal/handlers/mediamtx.go @@ -126,15 +126,46 @@ func (h *MediaMTXHandler) canPublish(sub, path string) bool { // --- 3.2 Mint publish token (device flow) -> returns WHIP URL // POST /mediamtx/token/publish {guid} func (h *MediaMTXHandler) MintPublish(c *gin.Context) { + user, ok := GetUserContext(c) + if !ok { + c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + return + } var req dto.PublishTokenReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "bad request"}) return } + + // Permission check (admin or assigned) + if user.Role != models.RoleAdmin { + var count int64 + _ = h.db.Table("user_devices"). + Where("user_id = ? AND device_guid = ?", user.ID, req.GUID). + Count(&count).Error + if count == 0 { + c.JSON(http.StatusForbidden, gin.H{"error": "not allowed for this device"}) + return + } + } + path := "live/" + req.GUID - tok, _ := h.jwtMgr.GenerateMediaToken(0, "publish", path, h.cfg.TokenTTL) // sub=0 (device) - whip := fmt.Sprintf("%s/whip/%s?token=%s", strings.TrimRight(h.cfg.WebRTCBaseURL, "/"), path, url.QueryEscape(tok)) - c.JSON(http.StatusCreated, dto.PublishTokenResp{WHIP: whip}) + + // We mint a *read* token for the browser to consume HLS. + tok, err := h.jwtMgr.GenerateMediaToken(user.ID, "read", path, h.cfg.TokenTTL) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "token error"}) + return + } + + pub := strings.TrimRight(h.cfg.PublicBaseURL, "/") + hls := fmt.Sprintf("%s/hls/%s/index.m3u8?token=%s", + pub, + path, + url.QueryEscape(tok), + ) + + c.JSON(http.StatusCreated, dto.PublishTokenResp{HLS: hls}) } // --- 3.3 Mint read token (user flow) -> returns HLS + WHEP URLs diff --git a/server/internal/router/router.go b/server/internal/router/router.go index a7189ce..eda96b0 100644 --- a/server/internal/router/router.go +++ b/server/internal/router/router.go @@ -81,7 +81,7 @@ func Build(db *gorm.DB, minio *minio.Client, cfg *config.Config) *gin.Engine { r.POST("/mediamtx/auth", mediamtxH.Auth) // Token minting for device/user flows r.POST("/mediamtx/token/publish", mediamtxH.MintPublish) - r.POST("/mediamtx/token/read", authMW, mediamtxH.MintRead) + r.POST("/mediamtx/token/read", authMW, middleware.DeviceAccessFilter(), mediamtxH.MintRead) // Admin controls r.GET("/mediamtx/paths", authMW, adminOnly, mediamtxH.ListPaths) r.POST("/mediamtx/webrtc/kick/:id", authMW, adminOnly, mediamtxH.KickWebRTC)