Skip to content

Commit 0ea3c0a

Browse files
authored
## Flutter addition (#21)
* feat: Add Flutter client for Random Video Streamer - Introduced a new Flutter client application for controlling and monitoring the Random Video Clips Streaming Server. - Implemented API endpoints for admin context, stats, and audio file retrieval. - Enhanced the dashboard with navigation and improved UI for better user experience. - Added support for audio file management and chunk generation controls. - Included necessary configuration files and dependencies for Flutter development. * feat: Implement route-based navigation and enhance admin and stats screens - Added `go_router` dependency for improved routing capabilities. - Introduced separate pages for Dashboard, Admin, and Stats with a unified header for navigation. - Enhanced Admin screen to display live status and system usage metrics. - Updated Stats screen to include pagination for model and audio statistics. - Refactored Home screen to streamline data loading and improve UI consistency. * feat: Update dependencies and enhance UI components - Added `google_fonts` and `url_launcher` dependencies for improved typography and link handling. - Introduced `video_player_web_hls` for enhanced HLS streaming support on web platforms. - Refactored the theme and UI components for better consistency and user experience across screens. - Updated README with detailed instructions for enabling live streaming and platform-specific notes.
1 parent b3e7944 commit 0ea3c0a

55 files changed

Lines changed: 3303 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ data/
4141
.DS_Store
4242
Thumbs.db
4343

44+
# Flutter
45+
.dart_tool/
46+
.flutter-plugins
47+
.flutter-plugins-dependencies
48+
.packages
49+
pubspec.lock
50+
flutter_client/build/
51+
4452
audio/
4553
chunks/
4654
stats/

app.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,12 @@ def admin():
558558
return render_template('admin.html', **ctx)
559559

560560

561+
@app.route('/api/admin-context')
562+
def api_admin_context():
563+
"""Admin data for API clients."""
564+
return jsonify(_admin_context())
565+
566+
561567
def _fetch_og_meta(url: str, timeout: float = 5.0) -> dict:
562568
"""Fetch og:title and og:image from URL. Returns {title, image} or empty dict on failure."""
563569
if not url or not url.startswith(('http://', 'https://')):
@@ -724,6 +730,9 @@ def _stats_context():
724730
'chunks_created_total': current_status.get('chunks_created_total'),
725731
'total_seconds_streamed': current_status.get('total_seconds_streamed'),
726732
}
733+
if not hasattr(clip_pusher, 'get_play_counts'):
734+
return {'stream_stats': stream_stats, 'play_counts': {'models': [], 'audio': []}}
735+
727736
play_counts = clip_pusher.get_play_counts()
728737
models_enriched = []
729738
for item in play_counts.get('models', []):
@@ -754,6 +763,12 @@ def stats():
754763
return render_template('stats.html', **ctx)
755764

756765

766+
@app.route('/api/stats')
767+
def api_stats():
768+
"""Stats data for API clients."""
769+
return jsonify(_stats_context())
770+
771+
757772
def _stream_url():
758773
"""Build HLS stream URL (respects X-Forwarded-Proto when behind HTTPS proxy)."""
759774
if request.scheme == 'https':
@@ -830,6 +845,21 @@ def api_chunks():
830845
return jsonify({'chunks': page_chunks, 'total': len(chunks)})
831846

832847

848+
@app.route('/api/audio')
849+
def api_audio():
850+
"""Get audio files with optional pagination (offset, limit)."""
851+
audio_files = []
852+
audio_extensions = ('.mp3', '.aac', '.flac', '.ogg', '.wav', '.m4a')
853+
if AUDIO_FOLDER and os.path.isdir(AUDIO_FOLDER):
854+
audio_files = _audio_files_with_durations(audio_extensions, AUDIO_FOLDER)
855+
856+
offset = max(0, int(request.args.get('offset', 0)))
857+
limit = min(200, max(1, int(request.args.get('limit', 50))))
858+
page_audio = audio_files[offset:offset + limit]
859+
current_audio = clip_pusher.get_status().get('current_audio')
860+
return jsonify({'audio_files': page_audio, 'total': len(audio_files), 'current_audio': current_audio})
861+
862+
833863
@app.route('/api/cron-run-history')
834864
def cron_run_history():
835865
"""Get chunk-generator cron/manual run history from stats/.cron_run_history (paginated)"""

flutter_client/.gitignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Miscellaneous
2+
*.class
3+
*.log
4+
*.pyc
5+
*.swp
6+
.DS_Store
7+
.atom/
8+
.build/
9+
.buildlog/
10+
.history
11+
.svn/
12+
.swiftpm/
13+
migrate_working_dir/
14+
15+
# IntelliJ related
16+
*.iml
17+
*.ipr
18+
*.iws
19+
.idea/
20+
21+
# The .vscode folder contains launch configuration and tasks you configure in
22+
# VS Code which you may wish to be included in version control, so this line
23+
# is commented out by default.
24+
#.vscode/
25+
26+
# Flutter/Dart/Pub related
27+
**/doc/api/
28+
**/ios/Flutter/.last_build_id
29+
.dart_tool/
30+
.flutter-plugins-dependencies
31+
.pub-cache/
32+
.pub/
33+
/build/
34+
/coverage/
35+
36+
# Symbolication related
37+
app.*.symbols
38+
39+
# Obfuscation related
40+
app.*.map.json
41+
42+
# Android Studio will place build artifacts here
43+
/android/app/debug
44+
/android/app/profile
45+
/android/app/release

flutter_client/.metadata

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# This file tracks properties of this Flutter project.
2+
# Used by Flutter tool to assess capabilities and perform upgrades etc.
3+
#
4+
# This file should be version controlled and should not be manually edited.
5+
6+
version:
7+
revision: "2c9eb20739dfec95e2c74bd3dfa4601b0a8a36aa"
8+
channel: "[user-branch]"
9+
10+
project_type: app
11+
12+
# Tracks metadata for the flutter migrate command
13+
migration:
14+
platforms:
15+
- platform: root
16+
create_revision: 2c9eb20739dfec95e2c74bd3dfa4601b0a8a36aa
17+
base_revision: 2c9eb20739dfec95e2c74bd3dfa4601b0a8a36aa
18+
- platform: android
19+
create_revision: 2c9eb20739dfec95e2c74bd3dfa4601b0a8a36aa
20+
base_revision: 2c9eb20739dfec95e2c74bd3dfa4601b0a8a36aa
21+
- platform: web
22+
create_revision: 2c9eb20739dfec95e2c74bd3dfa4601b0a8a36aa
23+
base_revision: 2c9eb20739dfec95e2c74bd3dfa4601b0a8a36aa
24+
25+
# User provided section
26+
27+
# List of Local paths (relative to this file) that should be
28+
# ignored by the migrate tool.
29+
#
30+
# Files that are not part of the templates will be ignored by default.
31+
unmanaged_files:
32+
- 'lib/main.dart'
33+
- 'ios/Runner.xcodeproj/project.pbxproj'

flutter_client/README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Flutter Client (POC)
2+
3+
This is a starter Flutter app for controlling and monitoring the Random Video Clips Streaming Server.
4+
5+
It now has separate route-based pages:
6+
7+
- `/#/` Dashboard
8+
- `/#/admin` Admin
9+
- `/#/stats` Stats
10+
11+
## What is implemented
12+
13+
- Live HLS player (`video_player` + **`video_player_web_hls`** on web + **`hls.js`** in `web/index.html`) — **off by default** (`ENABLE_LIVE_STREAM=true` to enable)
14+
- Stream status polling (`/api/stream-status`)
15+
- System usage polling (`/api/system-usage`)
16+
- Chunks list (`/api/chunks`)
17+
- Actions:
18+
- Skip video (`/api/skip_to_next`)
19+
- Skip audio (`/api/skip_to_next_audio`)
20+
- Generate chunks (`/api/generate_chunk`)
21+
- Play chunk next (`/api/play_chunk`)
22+
23+
## Prerequisites
24+
25+
- Backend server running and reachable from your device/emulator/browser
26+
- Docker installed
27+
28+
## Docker builds (recommended)
29+
30+
From repo root:
31+
32+
### Build web
33+
34+
```bash
35+
scripts/flutter-web-build-docker.sh \
36+
http://<server-ip>:8081 \
37+
http://<server-ip>:8082/hls/stream.m3u8
38+
```
39+
40+
Output:
41+
42+
- `flutter_client/build/web`
43+
44+
### Build Android APK
45+
46+
```bash
47+
scripts/flutter-android-build-docker.sh \
48+
apk \
49+
http://<server-ip>:8081 \
50+
http://<server-ip>:8082/hls/stream.m3u8
51+
```
52+
53+
Output:
54+
55+
- `flutter_client/dist/android/random-video-streamer-release.apk`
56+
57+
### Build Android App Bundle (Play Store)
58+
59+
```bash
60+
scripts/flutter-android-build-docker.sh \
61+
aab \
62+
http://<server-ip>:8081 \
63+
http://<server-ip>:8082/hls/stream.m3u8
64+
```
65+
66+
Output:
67+
68+
- `flutter_client/dist/android/random-video-streamer-release.aab`
69+
70+
Both scripts run `flutter create --platforms=android,web .` inside Docker, so platform folders are generated without local Flutter.
71+
72+
Live stream toggle (default is **off**; set `true` to embed the player):
73+
74+
```bash
75+
ENABLE_LIVE_STREAM=true scripts/flutter-web-build-docker.sh
76+
ENABLE_LIVE_STREAM=true scripts/flutter-android-build-docker.sh apk
77+
```
78+
79+
## Local Flutter run (optional)
80+
81+
```bash
82+
cd flutter_client
83+
flutter pub get
84+
flutter run \
85+
--dart-define=API_BASE_URL=http://<server-ip>:8081 \
86+
--dart-define=HLS_URL=http://<server-ip>:8082/hls/stream.m3u8 \
87+
--dart-define=ENABLE_LIVE_STREAM=false
88+
```
89+
90+
Optional:
91+
92+
```bash
93+
--dart-define=REFRESH_SECONDS=5
94+
```
95+
96+
## Notes by platform
97+
98+
- Android: HLS works with ExoPlayer; cleartext HTTP is allowed in the template manifest for LAN/dev streams.
99+
- **APK vs web UI port:** The Flutter **web** UI on **:8090** is only static files. The **API is still on :8081** (Flask). Build the APK with `API_BASE_URL=http://<server-ip>:8081`, **not** `:8090`. On the phone, open `http://<server-ip>:8081/api/status` in Chrome — if you get JSON, the app can use the same base URL. Rebuild the APK after adding `INTERNET` in `main` AndroidManifest if release builds had no network access.
100+
- Web: Chrome/Firefox need **HLS.js** (bundled via `index.html` + `video_player_web_hls`). The HLS origin must send **CORS** headers for `.m3u8` and segment requests. **Mixed content** (HTTPS page + HTTP stream) is blocked by the browser unless you proxy over HTTPS.
101+
- **LAN / `192.168.x.x`:** If the web build still has `localhost` in `API_BASE_URL` / `HLS_URL`, the client rewrites `localhost` / `127.0.0.1` to the **same host as the page** (e.g. opening `http://192.168.0.11:8090/` uses `http://192.168.0.11:8081` and `:8082` for HLS). Rebuild after pulling so this logic ships.
102+
- iOS is intentionally out of scope for this setup.
103+
104+
## Next steps
105+
106+
- Add auth if you expose this server beyond LAN.
107+
- Add dedicated screens for Stats/Admin APIs.
108+
- Add pagination and search for chunks and audio.
109+
- Add offline/error-state UX polish.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
include: package:flutter_lints/flutter.yaml
2+
3+
linter:
4+
rules:
5+
avoid_print: true

flutter_client/android/.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
gradle-wrapper.jar
2+
/.gradle
3+
/captures/
4+
/gradlew
5+
/gradlew.bat
6+
/local.properties
7+
GeneratedPluginRegistrant.java
8+
.cxx/
9+
10+
# Remember to never publicly share your keystore.
11+
# See https://flutter.dev/to/reference-keystore
12+
key.properties
13+
**/*.keystore
14+
**/*.jks
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
plugins {
2+
id("com.android.application")
3+
id("kotlin-android")
4+
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5+
id("dev.flutter.flutter-gradle-plugin")
6+
}
7+
8+
android {
9+
namespace = "com.example.random_video_streamer_client"
10+
compileSdk = flutter.compileSdkVersion
11+
ndkVersion = flutter.ndkVersion
12+
13+
compileOptions {
14+
sourceCompatibility = JavaVersion.VERSION_17
15+
targetCompatibility = JavaVersion.VERSION_17
16+
}
17+
18+
kotlinOptions {
19+
jvmTarget = JavaVersion.VERSION_17.toString()
20+
}
21+
22+
defaultConfig {
23+
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
24+
applicationId = "com.example.random_video_streamer_client"
25+
// You can update the following values to match your application needs.
26+
// For more information, see: https://flutter.dev/to/review-gradle-config.
27+
minSdk = flutter.minSdkVersion
28+
targetSdk = flutter.targetSdkVersion
29+
versionCode = flutter.versionCode
30+
versionName = flutter.versionName
31+
}
32+
33+
buildTypes {
34+
release {
35+
// TODO: Add your own signing config for the release build.
36+
// Signing with the debug keys for now, so `flutter run --release` works.
37+
signingConfig = signingConfigs.getByName("debug")
38+
}
39+
}
40+
}
41+
42+
flutter {
43+
source = "../.."
44+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<!-- The INTERNET permission is required for development. Specifically,
3+
the Flutter tool needs it to communicate with the running application
4+
to allow setting breakpoints, to provide hot reload, etc.
5+
-->
6+
<uses-permission android:name="android.permission.INTERNET"/>
7+
</manifest>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
2+
<!-- Required for release APKs to call LAN/HTTP API (debug manifest alone is not merged for release). -->
3+
<uses-permission android:name="android.permission.INTERNET"/>
4+
<application
5+
android:label="random_video_streamer_client"
6+
android:name="${applicationName}"
7+
android:icon="@mipmap/ic_launcher"
8+
android:usesCleartextTraffic="true">
9+
<activity
10+
android:name=".MainActivity"
11+
android:exported="true"
12+
android:launchMode="singleTop"
13+
android:taskAffinity=""
14+
android:theme="@style/LaunchTheme"
15+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
16+
android:hardwareAccelerated="true"
17+
android:windowSoftInputMode="adjustResize">
18+
<!-- Specifies an Android theme to apply to this Activity as soon as
19+
the Android process has started. This theme is visible to the user
20+
while the Flutter UI initializes. After that, this theme continues
21+
to determine the Window background behind the Flutter UI. -->
22+
<meta-data
23+
android:name="io.flutter.embedding.android.NormalTheme"
24+
android:resource="@style/NormalTheme"
25+
/>
26+
<intent-filter>
27+
<action android:name="android.intent.action.MAIN"/>
28+
<category android:name="android.intent.category.LAUNCHER"/>
29+
</intent-filter>
30+
</activity>
31+
<!-- Don't delete the meta-data below.
32+
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
33+
<meta-data
34+
android:name="flutterEmbedding"
35+
android:value="2" />
36+
</application>
37+
<!-- Required to query activities that can process text, see:
38+
https://developer.android.com/training/package-visibility and
39+
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
40+
41+
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
42+
<queries>
43+
<intent>
44+
<action android:name="android.intent.action.PROCESS_TEXT"/>
45+
<data android:mimeType="text/plain"/>
46+
</intent>
47+
</queries>
48+
</manifest>

0 commit comments

Comments
 (0)