een-api-toolkit 0.3.10 → 0.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +127 -7
- package/docs/AI-CONTEXT.md +1 -1
- package/examples/vue-media/README.md +154 -85
- package/examples/vue-media/e2e/app.spec.ts +8 -0
- package/examples/vue-media/e2e/auth.spec.ts +154 -2
- package/examples/vue-media/media-screenshot.png +0 -0
- package/examples/vue-media/package-lock.json +8 -1
- package/examples/vue-media/package.json +1 -0
- package/examples/vue-media/src/App.vue +3 -2
- package/examples/vue-media/src/composables/useSelectedDateTime.ts +102 -0
- package/examples/vue-media/src/router/index.ts +7 -0
- package/examples/vue-media/src/utils/timestamp.ts +263 -0
- package/examples/vue-media/src/views/HLS.vue +645 -0
- package/examples/vue-media/src/views/Home.vue +6 -2
- package/examples/vue-media/src/views/LiveCamera.vue +8 -27
- package/examples/vue-media/src/views/RecordedImage.vue +214 -77
- package/examples/vue-users/e2e/auth.spec.ts +3 -3
- package/examples/vue-users/package-lock.json +2 -2
- package/examples/vue-users/package.json +1 -1
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,24 +2,144 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## [0.3.
|
|
5
|
+
## [0.3.11] - 2026-01-12
|
|
6
6
|
|
|
7
7
|
### Release Summary
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
#### PR #52: Release v0.3.10: Vue-media improvements and agent definitions
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
This release includes improvements to the vue-media example and adds Claude Code agent definitions.
|
|
13
|
+
|
|
14
|
+
### Changes
|
|
15
|
+
|
|
16
|
+
- **feat(vue-media)**: Add seconds precision and Now button to recorded images
|
|
17
|
+
- Add seconds precision to datetime picker (`step="1"`)
|
|
18
|
+
- Add "Now" button to reset datetime picker to current time
|
|
19
|
+
- Update page titles to indicate "(Preview)" image quality
|
|
20
|
+
- Update README documentation with current API examples
|
|
21
|
+
|
|
22
|
+
- **chore**: Add Claude Code agent definitions
|
|
23
|
+
- `docs-accuracy-reviewer`: Verifies documentation accuracy
|
|
24
|
+
- `test-runner`: Runs unit and E2E test suites
|
|
25
|
+
|
|
26
|
+
### Commits
|
|
27
|
+
|
|
28
|
+
- 172ea1c chore: Add Claude Code agent definitions
|
|
29
|
+
- 142999e feat(vue-media): Add seconds precision and Now button to recorded images
|
|
30
|
+
|
|
31
|
+
## Test Results
|
|
32
|
+
|
|
33
|
+
- **Lint**: ✅ Passed (1 warning)
|
|
34
|
+
- **Unit Tests**: ✅ 188 passed
|
|
35
|
+
- **Build**: ✅ Successful
|
|
36
|
+
|
|
37
|
+
## Version
|
|
38
|
+
|
|
39
|
+
`0.3.10`
|
|
40
|
+
|
|
41
|
+
#### PR #53: feat(vue-media): Add HLS video streaming and improve media pages
|
|
42
|
+
## Summary
|
|
43
|
+
|
|
44
|
+
This PR adds HLS video streaming capability to the vue-media example and improves the existing media pages with better UX features.
|
|
45
|
+
|
|
46
|
+
### New Features
|
|
47
|
+
- **HLS Video page** - Stream recorded video using HLS protocol with adaptive bitrate support
|
|
48
|
+
- **Shared datetime state** - Datetime selection persists across Recorded Image and HLS Video pages
|
|
49
|
+
- **Clip Time button** - Quickly set datetime picker to the clip's start time
|
|
50
|
+
- **Time position indicator** - Shows whether selected time is before, inside, or after the segment
|
|
51
|
+
- **Image resolution display** - Shows dimensions for preview and main images on Recorded Image page
|
|
52
|
+
|
|
53
|
+
### Changes
|
|
54
|
+
- Add HLS.vue page for HLS video streaming
|
|
55
|
+
- Add useSelectedDateTime composable for shared datetime state
|
|
56
|
+
- Display segment start/end times with duration on HLS page
|
|
57
|
+
- Use YYYY-MM-DD date format and AM/PM time notation
|
|
58
|
+
- Remove MP4 playback page (simplified to HLS only)
|
|
59
|
+
- Update README with comprehensive documentation of all pages and APIs
|
|
60
|
+
- Add E2E tests for HLS video page
|
|
61
|
+
|
|
62
|
+
### Commits
|
|
63
|
+
- e374bd6 feat(vue-media): Add HLS video streaming and improve media pages
|
|
64
|
+
- 14b8d71 fix(vue-media): Now button triggers image fetch like Go button
|
|
65
|
+
- f3bb8bf Merge pull request #52 from klaushofrichter/develop
|
|
66
|
+
- 6368e35 fix: Address code review feedback and test timeout issue
|
|
67
|
+
- 172ea1c chore: Add Claude Code agent definitions
|
|
68
|
+
- 142999e feat(vue-media): Add seconds precision and Now button to recorded images
|
|
69
|
+
|
|
70
|
+
## Test Results
|
|
71
|
+
- **Lint**: Passed (1 warning)
|
|
72
|
+
- **Unit Tests**: 188 passed
|
|
73
|
+
- **Build**: Success
|
|
74
|
+
|
|
75
|
+
## Version
|
|
76
|
+
`0.3.10`
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
🤖 Generated with [Claude Code](https://claude.ai/code)
|
|
80
|
+
|
|
81
|
+
#### PR #54: chore: Audit and upgrade dependencies
|
|
82
|
+
## Summary
|
|
83
|
+
|
|
84
|
+
- Adds `@eslint/js` package for ESLint v9 flat config compatibility
|
|
85
|
+
- Merges latest develop changes (conflict resolution)
|
|
86
|
+
- Removes accidentally committed non-code files
|
|
87
|
+
|
|
88
|
+
## Changes
|
|
89
|
+
|
|
90
|
+
This branch was originally created to audit dependencies but diverged from develop. The main contribution is:
|
|
91
|
+
|
|
92
|
+
- **ESLint v9 compatibility**: Added `@eslint/js` package required for the flat config format
|
|
93
|
+
|
|
94
|
+
## Conflict Resolution
|
|
95
|
+
|
|
96
|
+
Resolved merge conflicts with develop:
|
|
97
|
+
- `eslint.config.js`: Adopted develop's `commonRules` pattern
|
|
98
|
+
- `package.json`: Merged dependencies
|
|
99
|
+
- `vite.config.ts`: Used develop's test exclude pattern
|
|
100
|
+
- Removed obsolete `examples/vue-basic/` (renamed to `vue-users` in develop)
|
|
101
|
+
|
|
102
|
+
## Test Results
|
|
103
|
+
|
|
104
|
+
- Linting: Passed (1 warning - existing console statement)
|
|
105
|
+
- Unit tests: 188/188 passed
|
|
106
|
+
- Build: Successful
|
|
107
|
+
|
|
108
|
+
## Version
|
|
109
|
+
|
|
110
|
+
`0.3.11`
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
Generated with [Claude Code](https://claude.ai/code)
|
|
114
|
+
|
|
10
115
|
|
|
11
116
|
### Detailed Changes
|
|
12
117
|
|
|
13
118
|
#### Features
|
|
14
|
-
- feat: Add
|
|
15
|
-
- feat: Add
|
|
119
|
+
- feat(vue-media): Add 60 second loading timeout for HLS video streams
|
|
120
|
+
- feat(vue-media): Add HLS video streaming and improve media pages
|
|
121
|
+
- feat(vue-media): Add seconds precision and Now button to recorded images
|
|
122
|
+
|
|
123
|
+
#### Bug Fixes
|
|
124
|
+
- fix(vue-media): Improve UI labels and layout
|
|
125
|
+
- fix(vue-media): Address remaining code review issues
|
|
126
|
+
- fix(vue-media): Address code review high/medium priority issues
|
|
127
|
+
- fix(vue-media): Now button triggers image fetch like Go button
|
|
128
|
+
- fix: Address code review feedback and test timeout issue
|
|
16
129
|
|
|
17
130
|
#### Other Changes
|
|
18
|
-
-
|
|
131
|
+
- chore: Address code review recommendations
|
|
132
|
+
- chore: Consolidate .gitignore presentation patterns
|
|
133
|
+
- chore: Add presentation artifacts to .gitignore
|
|
134
|
+
- chore: Remove accidentally committed non-code files
|
|
135
|
+
- chore: Update example dependencies and fix test configuration
|
|
136
|
+
- refactor(vue-media): Extract timestamp utilities and fix code review issues
|
|
137
|
+
- chore: Add Claude Code agent definitions
|
|
138
|
+
- chore: Upgrade all optional dependencies to latest major versions
|
|
19
139
|
|
|
20
140
|
### Links
|
|
21
141
|
- [npm package](https://www.npmjs.com/package/een-api-toolkit)
|
|
22
|
-
- [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.
|
|
142
|
+
- [Full Changelog](https://github.com/klaushofrichter/een-api-toolkit/compare/v0.3.10...v0.3.11)
|
|
23
143
|
|
|
24
144
|
---
|
|
25
|
-
*Released: 2026-01-
|
|
145
|
+
*Released: 2026-01-12 17:56:08 CST*
|
package/docs/AI-CONTEXT.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# EEN API Toolkit - Vue Media Example
|
|
2
2
|
|
|
3
|
-
A Vue 3 example demonstrating how to fetch live
|
|
3
|
+
A Vue 3 example demonstrating how to fetch live images, recorded images, and stream HLS video from EEN cameras using the een-api-toolkit.
|
|
4
4
|
|
|
5
5
|

|
|
6
6
|
|
|
@@ -19,21 +19,78 @@ This is a good balance between security (limiting XSS blast radius) and user exp
|
|
|
19
19
|
|
|
20
20
|
- OAuth authentication flow (login, callback, logout)
|
|
21
21
|
- Protected routes with navigation guards
|
|
22
|
-
- `getCameras()` function for listing cameras
|
|
23
|
-
- `getLiveImage()` function for fetching live preview images
|
|
24
|
-
- `getRecordedImage()` function for fetching recorded images with navigation
|
|
25
22
|
- Camera selection with persistence across pages
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
23
|
+
- Live image viewing with auto-refresh
|
|
24
|
+
- Recorded image viewing with prev/next navigation
|
|
25
|
+
- HLS video streaming with adaptive bitrate
|
|
26
|
+
- Datetime picker with seconds precision
|
|
27
|
+
- Shared datetime state across pages
|
|
28
|
+
|
|
29
|
+
## Pages Overview
|
|
30
|
+
|
|
31
|
+
### Home Page
|
|
32
|
+
The landing page that displays a welcome message and login prompt when not authenticated. Shows the available toolkit functions and their descriptions.
|
|
33
|
+
|
|
34
|
+
### Live Camera Image
|
|
35
|
+
Displays live preview images from the selected camera with automatic refresh every 5 seconds.
|
|
36
|
+
|
|
37
|
+
**APIs Used:**
|
|
38
|
+
- `getCameras()` - Lists available cameras for selection
|
|
39
|
+
- `getLiveImage()` - Fetches the current live preview image as base64
|
|
40
|
+
|
|
41
|
+
**Features:**
|
|
42
|
+
- Camera selector dropdown
|
|
43
|
+
- Auto-refresh with error recovery (stops after 3 consecutive failures)
|
|
44
|
+
- Displays image timestamp
|
|
45
|
+
|
|
46
|
+
### Recorded Image
|
|
47
|
+
Displays recorded images at a specific point in time, showing both preview and main quality images side by side.
|
|
48
|
+
|
|
49
|
+
**APIs Used:**
|
|
50
|
+
- `getCameras()` - Lists available cameras for selection
|
|
51
|
+
- `getRecordedImage()` - Fetches recorded images with pagination tokens
|
|
52
|
+
|
|
53
|
+
**Features:**
|
|
54
|
+
- Camera selector dropdown
|
|
55
|
+
- Datetime picker with seconds precision
|
|
56
|
+
- Previous/Next navigation using pagination tokens
|
|
57
|
+
- "Now" button to reset to current time
|
|
58
|
+
- Displays both preview and main quality images
|
|
59
|
+
- Shows image resolution (e.g., 640x360 for preview, 1920x1080 for main)
|
|
60
|
+
- Displays UTC timestamp in EEN API format
|
|
61
|
+
|
|
62
|
+
### HLS Video
|
|
63
|
+
Streams recorded video using HLS (HTTP Live Streaming) protocol with adaptive bitrate support.
|
|
64
|
+
|
|
65
|
+
**APIs Used:**
|
|
66
|
+
- `getCameras()` - Lists available cameras for selection
|
|
67
|
+
- `initMediaSession()` - Initializes the media session (required for video URLs)
|
|
68
|
+
- `listMedia()` - Retrieves media intervals with HLS URLs
|
|
69
|
+
- `useAuthStore()` - Explicitly used to get the token for HLS.js requests (see note below)
|
|
70
|
+
|
|
71
|
+
**Features:**
|
|
72
|
+
- Camera selector dropdown
|
|
73
|
+
- Datetime picker with seconds precision
|
|
74
|
+
- "Now" button to reset to current time
|
|
75
|
+
- "Clip Time" button to set picker to the clip's start time
|
|
76
|
+
- Displays segment start/end times with duration
|
|
77
|
+
- Shows whether selected time is before, inside, or after the segment
|
|
78
|
+
- Uses HLS.js library for cross-browser HLS support
|
|
79
|
+
- Adds Authorization header to HLS segment requests
|
|
80
|
+
|
|
81
|
+
## APIs Used Summary
|
|
82
|
+
|
|
83
|
+
| API Function | Pages | Purpose |
|
|
84
|
+
|--------------|-------|---------|
|
|
85
|
+
| `getCameras()` | All media pages | List available cameras |
|
|
86
|
+
| `getLiveImage()` | Live Camera Image | Fetch live preview image |
|
|
87
|
+
| `getRecordedImage()` | Recorded Image | Fetch recorded images with navigation |
|
|
88
|
+
| `initMediaSession()` | HLS Video | Initialize media session for video URLs |
|
|
89
|
+
| `listMedia()` | HLS Video | Get media intervals with streaming URLs |
|
|
90
|
+
| `useAuthStore()` | All pages | Authentication state management |
|
|
91
|
+
| `initEenToolkit()` | App initialization | Configure toolkit settings |
|
|
92
|
+
|
|
93
|
+
**Note on `useAuthStore()`:** All toolkit functions (`getCameras`, `getLiveImage`, `getRecordedImage`, `listMedia`) use `useAuthStore()` internally to get the authentication token. The HLS Video page is the only one that explicitly calls `useAuthStore()` in its code because HLS.js is a third-party library that makes its own HTTP requests - the token must be manually passed to HLS.js via the `xhrSetup` callback.
|
|
37
94
|
|
|
38
95
|
## Setup
|
|
39
96
|
|
|
@@ -65,7 +122,7 @@ All commands below should be run from this example directory (`examples/vue-medi
|
|
|
65
122
|
3. Edit `.env` with your EEN credentials:
|
|
66
123
|
```env
|
|
67
124
|
VITE_EEN_CLIENT_ID=your-client-id
|
|
68
|
-
VITE_PROXY_URL=http://
|
|
125
|
+
VITE_PROXY_URL=http://127.0.0.1:8787
|
|
69
126
|
# DO NOT change the redirect URI - EEN IDP only permits this URL
|
|
70
127
|
VITE_REDIRECT_URI=http://127.0.0.1:3333
|
|
71
128
|
```
|
|
@@ -87,17 +144,21 @@ All commands below should be run from this example directory (`examples/vue-medi
|
|
|
87
144
|
|
|
88
145
|
```
|
|
89
146
|
src/
|
|
90
|
-
├── main.ts
|
|
91
|
-
├── App.vue
|
|
147
|
+
├── main.ts # App entry, toolkit initialization
|
|
148
|
+
├── App.vue # Root component with navigation
|
|
92
149
|
├── router/
|
|
93
|
-
│ └── index.ts
|
|
150
|
+
│ └── index.ts # Vue Router with auth guards
|
|
151
|
+
├── composables/
|
|
152
|
+
│ ├── useSelectedCamera.ts # Shared camera selection state
|
|
153
|
+
│ └── useSelectedDateTime.ts # Shared datetime state
|
|
94
154
|
└── views/
|
|
95
|
-
├── Home.vue
|
|
96
|
-
├── Login.vue
|
|
97
|
-
├── Callback.vue
|
|
98
|
-
├── LiveCamera.vue
|
|
99
|
-
├── RecordedImage.vue # Recorded image viewer
|
|
100
|
-
|
|
155
|
+
├── Home.vue # Home page with login prompt
|
|
156
|
+
├── Login.vue # OAuth login redirect
|
|
157
|
+
├── Callback.vue # OAuth callback handler
|
|
158
|
+
├── LiveCamera.vue # Live image viewer with auto-refresh
|
|
159
|
+
├── RecordedImage.vue # Recorded image viewer (preview + main)
|
|
160
|
+
├── HLS.vue # HLS video streaming
|
|
161
|
+
└── Logout.vue # Logout handler
|
|
101
162
|
```
|
|
102
163
|
|
|
103
164
|
## Key Code Examples
|
|
@@ -105,94 +166,102 @@ src/
|
|
|
105
166
|
### Fetching Live Images (LiveCamera.vue)
|
|
106
167
|
|
|
107
168
|
```typescript
|
|
108
|
-
import { getLiveImage
|
|
169
|
+
import { getLiveImage } from 'een-api-toolkit'
|
|
109
170
|
|
|
110
171
|
async function fetchLiveImage() {
|
|
111
|
-
const result = await getLiveImage(selectedCameraId.value
|
|
112
|
-
type: 'preview'
|
|
113
|
-
})
|
|
172
|
+
const result = await getLiveImage({ deviceId: selectedCameraId.value })
|
|
114
173
|
|
|
115
174
|
if (result.error) {
|
|
116
175
|
error.value = result.error.message
|
|
117
176
|
} else {
|
|
118
|
-
imageData.value = result.data.
|
|
177
|
+
imageData.value = result.data.imageData
|
|
119
178
|
timestamp.value = result.data.timestamp
|
|
120
179
|
}
|
|
121
180
|
}
|
|
122
181
|
```
|
|
123
182
|
|
|
124
|
-
### Auto-Refresh for Live Images
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
let refreshInterval: number | null = null
|
|
128
|
-
|
|
129
|
-
function startAutoRefresh() {
|
|
130
|
-
refreshInterval = window.setInterval(() => {
|
|
131
|
-
fetchLiveImage()
|
|
132
|
-
}, 2000) // Refresh every 2 seconds
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function stopAutoRefresh() {
|
|
136
|
-
if (refreshInterval) {
|
|
137
|
-
clearInterval(refreshInterval)
|
|
138
|
-
refreshInterval = null
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
183
|
### Fetching Recorded Images (RecordedImage.vue)
|
|
144
184
|
|
|
145
|
-
|
|
146
|
-
import { getRecordedImage, type RecordedImageParams } from 'een-api-toolkit'
|
|
147
|
-
|
|
148
|
-
async function fetchRecordedImage() {
|
|
149
|
-
const result = await getRecordedImage(selectedCameraId.value, {
|
|
150
|
-
timestamp__gte: selectedTimestamp.value,
|
|
151
|
-
type: 'preview'
|
|
152
|
-
})
|
|
185
|
+
Fetches both preview and main quality images for comparison:
|
|
153
186
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
187
|
+
```typescript
|
|
188
|
+
import { getRecordedImage } from 'een-api-toolkit'
|
|
189
|
+
|
|
190
|
+
// Fetch preview image
|
|
191
|
+
const result = await getRecordedImage({
|
|
192
|
+
deviceId: selectedCameraId.value,
|
|
193
|
+
type: 'preview',
|
|
194
|
+
timestamp__gte: timestamp
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Fetch main image at the same timestamp
|
|
198
|
+
const mainResult = await getRecordedImage({
|
|
199
|
+
deviceId: selectedCameraId.value,
|
|
200
|
+
type: 'main',
|
|
201
|
+
timestamp__gte: result.data.timestamp
|
|
202
|
+
})
|
|
163
203
|
```
|
|
164
204
|
|
|
165
|
-
###
|
|
205
|
+
### Streaming HLS Video (HLS.vue)
|
|
166
206
|
|
|
167
207
|
```typescript
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
208
|
+
import { listMedia, initMediaSession, useAuthStore } from 'een-api-toolkit'
|
|
209
|
+
import Hls from 'hls.js'
|
|
210
|
+
|
|
211
|
+
// Initialize media session first
|
|
212
|
+
await initMediaSession()
|
|
213
|
+
|
|
214
|
+
// Get HLS URL from listMedia
|
|
215
|
+
const result = await listMedia({
|
|
216
|
+
deviceId: selectedCameraId.value,
|
|
217
|
+
type: 'main',
|
|
218
|
+
mediaType: 'video',
|
|
219
|
+
startTimestamp: timestamp,
|
|
220
|
+
include: ['hlsUrl']
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// Configure HLS.js with Authorization header
|
|
224
|
+
const authStore = useAuthStore()
|
|
225
|
+
const hls = new Hls({
|
|
226
|
+
xhrSetup: (xhr: XMLHttpRequest) => {
|
|
227
|
+
xhr.setRequestHeader('Authorization', `Bearer ${authStore.token}`)
|
|
181
228
|
}
|
|
182
|
-
}
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
hls.loadSource(result.data.results[0].hlsUrl)
|
|
232
|
+
hls.attachMedia(videoElement)
|
|
183
233
|
```
|
|
184
234
|
|
|
185
235
|
### Displaying Images
|
|
186
236
|
|
|
237
|
+
The toolkit returns `imageData` as a complete data URL (including the `data:image/jpeg;base64,` prefix), so it can be used directly with `:src`:
|
|
238
|
+
|
|
187
239
|
```vue
|
|
188
240
|
<template>
|
|
189
241
|
<img
|
|
190
242
|
v-if="imageData"
|
|
191
|
-
:src="
|
|
243
|
+
:src="imageData"
|
|
192
244
|
alt="Camera image"
|
|
193
245
|
/>
|
|
194
|
-
<p v-if="timestamp">
|
|
195
|
-
Timestamp: {{ new Date(timestamp).toLocaleString() }}
|
|
196
|
-
</p>
|
|
197
246
|
</template>
|
|
198
247
|
```
|
|
248
|
+
|
|
249
|
+
### Shared State with Composables
|
|
250
|
+
|
|
251
|
+
Camera selection and datetime are shared across pages using Vue composables:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// useSelectedCamera.ts
|
|
255
|
+
import { ref } from 'vue'
|
|
256
|
+
|
|
257
|
+
const selectedCameraId = ref<string | null>(null)
|
|
258
|
+
|
|
259
|
+
export function useSelectedCamera() {
|
|
260
|
+
return {
|
|
261
|
+
selectedCameraId,
|
|
262
|
+
setSelectedCamera: (id: string) => { selectedCameraId.value = id }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
This allows users to switch between Live Camera, Recorded Image, and HLS Video pages while maintaining their camera and time selection.
|
|
@@ -52,6 +52,7 @@ test.describe('vue-media example app', () => {
|
|
|
52
52
|
await expect(page.getByText('getCameras()')).toBeVisible()
|
|
53
53
|
await expect(page.getByText('getLiveImage()')).toBeVisible()
|
|
54
54
|
await expect(page.getByText('getRecordedImage()')).toBeVisible()
|
|
55
|
+
await expect(page.getByText('listMedia()')).toBeVisible()
|
|
55
56
|
})
|
|
56
57
|
|
|
57
58
|
test('recorded route redirects to login when not authenticated', async ({ page }) => {
|
|
@@ -60,4 +61,11 @@ test.describe('vue-media example app', () => {
|
|
|
60
61
|
// Should redirect to login page (auth guard)
|
|
61
62
|
await expect(page).toHaveURL('/login')
|
|
62
63
|
})
|
|
64
|
+
|
|
65
|
+
test('hls route redirects to login when not authenticated', async ({ page }) => {
|
|
66
|
+
await page.goto('/hls')
|
|
67
|
+
|
|
68
|
+
// Should redirect to login page (auth guard)
|
|
69
|
+
await expect(page).toHaveURL('/login')
|
|
70
|
+
})
|
|
63
71
|
})
|
|
@@ -201,7 +201,7 @@ test.describe('Vue Media Example - Auth', () => {
|
|
|
201
201
|
await page.click('[data-testid="nav-live"]')
|
|
202
202
|
await page.waitForURL('/live')
|
|
203
203
|
|
|
204
|
-
await expect(page.getByRole('heading', { name: 'Live Camera
|
|
204
|
+
await expect(page.getByRole('heading', { name: 'Live Camera Image (preview)' })).toBeVisible()
|
|
205
205
|
|
|
206
206
|
// Wait for cameras to load
|
|
207
207
|
await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
|
|
@@ -243,7 +243,7 @@ test.describe('Vue Media Example - Auth', () => {
|
|
|
243
243
|
await page.click('[data-testid="nav-recorded"]')
|
|
244
244
|
await page.waitForURL('/recorded')
|
|
245
245
|
|
|
246
|
-
await expect(page.getByRole('heading', { name: 'Recorded
|
|
246
|
+
await expect(page.getByRole('heading', { name: 'Recorded Image (Preview and Main)' })).toBeVisible()
|
|
247
247
|
|
|
248
248
|
// Wait for cameras to load
|
|
249
249
|
await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
|
|
@@ -269,6 +269,7 @@ test.describe('Vue Media Example - Auth', () => {
|
|
|
269
269
|
if (hasCameras) {
|
|
270
270
|
await expect(page.getByTestId('datetime-input')).toBeVisible()
|
|
271
271
|
await expect(page.getByTestId('go-button')).toBeVisible()
|
|
272
|
+
await expect(page.getByTestId('now-button')).toBeVisible()
|
|
272
273
|
await expect(page.getByTestId('prev-button')).toBeVisible()
|
|
273
274
|
await expect(page.getByTestId('next-button')).toBeVisible()
|
|
274
275
|
console.log('Recorded image controls visible')
|
|
@@ -277,6 +278,157 @@ test.describe('Vue Media Example - Auth', () => {
|
|
|
277
278
|
}
|
|
278
279
|
})
|
|
279
280
|
|
|
281
|
+
test('Now button resets datetime picker to current time', async ({ page }) => {
|
|
282
|
+
skipIfNoProxy()
|
|
283
|
+
skipIfNoCredentials()
|
|
284
|
+
|
|
285
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
286
|
+
await expect(page.getByTestId('nav-recorded')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
287
|
+
|
|
288
|
+
await page.click('[data-testid="nav-recorded"]')
|
|
289
|
+
await page.waitForURL('/recorded')
|
|
290
|
+
|
|
291
|
+
await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
|
|
292
|
+
timeout: TIMEOUTS.MEDIA_LOAD
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
|
|
296
|
+
if (hasCameras) {
|
|
297
|
+
const datetimeInput = page.getByTestId('datetime-input')
|
|
298
|
+
await expect(datetimeInput).toBeVisible()
|
|
299
|
+
|
|
300
|
+
// Set datetime to a past time (1 hour ago)
|
|
301
|
+
const pastTime = new Date(Date.now() - 60 * 60 * 1000)
|
|
302
|
+
const pastTimeStr = pastTime.toISOString().slice(0, 19) // Format: YYYY-MM-DDTHH:mm:ss
|
|
303
|
+
await datetimeInput.fill(pastTimeStr)
|
|
304
|
+
|
|
305
|
+
// Verify the input has the past time
|
|
306
|
+
const valueBeforeClick = await datetimeInput.inputValue()
|
|
307
|
+
expect(valueBeforeClick).toContain(pastTimeStr.slice(0, 16)) // Check date and time portion
|
|
308
|
+
|
|
309
|
+
// Click the Now button
|
|
310
|
+
await page.click('[data-testid="now-button"]')
|
|
311
|
+
|
|
312
|
+
// Get the new value and verify it's closer to current time
|
|
313
|
+
const valueAfterClick = await datetimeInput.inputValue()
|
|
314
|
+
const nowTime = new Date()
|
|
315
|
+
const selectedTime = new Date(valueAfterClick)
|
|
316
|
+
|
|
317
|
+
// The selected time should be within 2 minutes of now
|
|
318
|
+
const timeDiffMs = Math.abs(nowTime.getTime() - selectedTime.getTime())
|
|
319
|
+
expect(timeDiffMs).toBeLessThan(2 * 60 * 1000) // 2 minutes tolerance
|
|
320
|
+
|
|
321
|
+
// Verify UTC timestamp is visible and in valid EEN API format
|
|
322
|
+
const utcTimestamp = page.getByTestId('utc-timestamp')
|
|
323
|
+
await expect(utcTimestamp).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
324
|
+
|
|
325
|
+
const utcText = await utcTimestamp.textContent()
|
|
326
|
+
expect(utcText).toContain('Timestamp for API (UTC):')
|
|
327
|
+
|
|
328
|
+
// Extract the timestamp value and verify EEN API format: YYYY-MM-DDTHH:mm:ss.sss+00:00
|
|
329
|
+
const apiTimestampMatch = utcText?.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}\+00:00/)
|
|
330
|
+
expect(apiTimestampMatch).not.toBeNull()
|
|
331
|
+
|
|
332
|
+
console.log('Now button correctly reset datetime to current time')
|
|
333
|
+
console.log('UTC timestamp visible and valid:', apiTimestampMatch?.[0])
|
|
334
|
+
} else {
|
|
335
|
+
console.log('No cameras in account - skipping Now button test')
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
test('datetime selection persists between recorded and video pages', async ({ page }) => {
|
|
340
|
+
skipIfNoProxy()
|
|
341
|
+
skipIfNoCredentials()
|
|
342
|
+
|
|
343
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
344
|
+
await expect(page.getByTestId('nav-recorded')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
345
|
+
|
|
346
|
+
// Navigate to recorded page
|
|
347
|
+
await page.click('[data-testid="nav-recorded"]')
|
|
348
|
+
await page.waitForURL('/recorded')
|
|
349
|
+
|
|
350
|
+
await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
|
|
351
|
+
timeout: TIMEOUTS.MEDIA_LOAD
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
|
|
355
|
+
if (hasCameras) {
|
|
356
|
+
const datetimeInput = page.getByTestId('datetime-input')
|
|
357
|
+
await expect(datetimeInput).toBeVisible()
|
|
358
|
+
|
|
359
|
+
// Set a specific datetime (2 hours ago to ensure it's different from default)
|
|
360
|
+
const specificTime = new Date(Date.now() - 2 * 60 * 60 * 1000)
|
|
361
|
+
const specificTimeStr = specificTime.toISOString().slice(0, 19) // Format: YYYY-MM-DDTHH:mm:ss
|
|
362
|
+
await datetimeInput.fill(specificTimeStr)
|
|
363
|
+
|
|
364
|
+
// Verify the input has the specific time
|
|
365
|
+
const valueOnRecorded = await datetimeInput.inputValue()
|
|
366
|
+
expect(valueOnRecorded).toContain(specificTimeStr.slice(0, 16)) // Check date and time portion
|
|
367
|
+
|
|
368
|
+
// Navigate to HLS video page
|
|
369
|
+
await page.click('[data-testid="nav-hls"]')
|
|
370
|
+
await page.waitForURL('/hls')
|
|
371
|
+
|
|
372
|
+
await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
|
|
373
|
+
timeout: TIMEOUTS.MEDIA_LOAD
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
// Verify the datetime is persisted on HLS page
|
|
377
|
+
const hlsDatetimeInput = page.getByTestId('datetime-input')
|
|
378
|
+
await expect(hlsDatetimeInput).toBeVisible()
|
|
379
|
+
|
|
380
|
+
const valueOnHls = await hlsDatetimeInput.inputValue()
|
|
381
|
+
expect(valueOnHls).toContain(specificTimeStr.slice(0, 16)) // Should match the time set on recorded page
|
|
382
|
+
|
|
383
|
+
console.log('Datetime persistence verified: recorded →', valueOnRecorded, '| hls →', valueOnHls)
|
|
384
|
+
} else {
|
|
385
|
+
console.log('No cameras in account - skipping datetime persistence test')
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
test('can view HLS video after login', async ({ page }) => {
|
|
390
|
+
skipIfNoProxy()
|
|
391
|
+
skipIfNoCredentials()
|
|
392
|
+
|
|
393
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
394
|
+
await expect(page.getByTestId('nav-hls')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
395
|
+
|
|
396
|
+
await page.click('[data-testid="nav-hls"]')
|
|
397
|
+
await page.waitForURL('/hls')
|
|
398
|
+
|
|
399
|
+
await expect(page.getByRole('heading', { name: 'HLS Video Streaming (Main)' })).toBeVisible()
|
|
400
|
+
|
|
401
|
+
// Wait for cameras to load
|
|
402
|
+
await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
|
|
403
|
+
timeout: TIMEOUTS.MEDIA_LOAD
|
|
404
|
+
})
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
test('HLS page shows controls when cameras available', async ({ page }) => {
|
|
408
|
+
skipIfNoProxy()
|
|
409
|
+
skipIfNoCredentials()
|
|
410
|
+
|
|
411
|
+
await performLogin(page, TEST_USER!, TEST_PASSWORD!)
|
|
412
|
+
await expect(page.getByTestId('nav-hls')).toBeVisible({ timeout: TIMEOUTS.UI_UPDATE })
|
|
413
|
+
|
|
414
|
+
await page.click('[data-testid="nav-hls"]')
|
|
415
|
+
await page.waitForURL('/hls')
|
|
416
|
+
|
|
417
|
+
await page.waitForSelector('[data-testid="camera-select"], .no-cameras', {
|
|
418
|
+
timeout: TIMEOUTS.MEDIA_LOAD
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
const hasCameras = await page.getByTestId('camera-select').isVisible().catch(() => false)
|
|
422
|
+
if (hasCameras) {
|
|
423
|
+
await expect(page.getByTestId('datetime-input')).toBeVisible()
|
|
424
|
+
await expect(page.getByTestId('go-button')).toBeVisible()
|
|
425
|
+
await expect(page.getByTestId('now-button')).toBeVisible()
|
|
426
|
+
console.log('HLS video controls visible')
|
|
427
|
+
} else {
|
|
428
|
+
console.log('No cameras in account - skipping camera-specific checks')
|
|
429
|
+
}
|
|
430
|
+
})
|
|
431
|
+
|
|
280
432
|
test('can logout after login', async ({ page }) => {
|
|
281
433
|
skipIfNoProxy()
|
|
282
434
|
skipIfNoCredentials()
|
|
Binary file
|