feat: android build

This commit is contained in:
kieran 2024-10-17 20:06:37 +01:00
parent 6017ce18d4
commit 91f0baf75c
No known key found for this signature in database
GPG Key ID: DE71CEB3925BE941
11 changed files with 501 additions and 318 deletions

3
.gitignore vendored
View File

@ -2,4 +2,5 @@
/lock.mdb /lock.mdb
/data.mdb /data.mdb
/.idea /.idea
/cache /cache
/ffmpeg-kit

713
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,32 +19,17 @@ pretty_env_logger = "0.5.0"
egui_inbox = "0.6.0" egui_inbox = "0.6.0"
bech32 = "0.11.0" bech32 = "0.11.0"
libc = "0.2.158" libc = "0.2.158"
egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "4766d939ce4d34b5a3a57b2fbe750ea10f389f39" }
uuid = { version = "1.11.0", features = ["v4"] } uuid = { version = "1.11.0", features = ["v4"] }
chrono = "0.4.38" chrono = "0.4.38"
anyhow = "1.0.89" anyhow = "1.0.89"
async-trait = "0.1.83" async-trait = "0.1.83"
sha2 = "0.10.8" sha2 = "0.10.8"
reqwest = { version = "0.12.7", default-features = false, features = [ "rustls-tls-native-roots" ] } reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls-native-roots"] }
itertools = "0.13.0" itertools = "0.13.0"
egui-video = { git = "https://github.com/v0l/egui-video.git", rev = "7e9f8dbc2c7d69dd2f327b18f6dc3687d65129b4" }
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.14.1" android_logger = "0.14.1"
android-activity = { version = "0.6.0", features = [ "native-activity" ] } android-activity = { version = "0.6.0", features = ["native-activity"] }
winit = { version = "0.30.5", features = [ "android-native-activity" ] } winit = { version = "0.30.5", features = ["android-native-activity"] }
[package.metadata.android]
package = "stream.zap.app"
build_targets = [ "aarch64-linux-android" ]
#build_targets = [ "armv7-linux-androideabi" ]
large_heap = true
[package.metadata.android.sdk]
min_sdk_version = 20
target_sdk_version = 32
[package.metadata.android.application]
extract_native_libs = true
[package.metadata.android.application.activity]
config_changes = "orientation"

28
android.sh Executable file
View File

@ -0,0 +1,28 @@
#!/bin/bash
git clone https://github.com/v0l/ffmpeg-kit.git
export ANDROID_SDK_ROOT=$ANDROID_HOME
#cd ffmpeg-kit && ./android.sh \
# --disable-x86 \
# --disable-x86-64 \
# --disable-arm-v7a \
# --disable-arm-v7a-neon \
# --no-ffmpeg-kit-protocols \
# --no-archive
if [[ $? -ne 0 ]]; then
exit 1;
fi
NDK_VER="28.0.12433566"
ARCH="arm64"
PLATFORM="android"
TRIPLET="aarch64-linux-android"
export PKG_CONFIG_SYSROOT_DIR="/"
export FFMPEG_DIR="$(pwd)/ffmpeg-kit/prebuilt/$PLATFORM-$ARCH/ffmpeg"
# DIRTY HACK !!
cp "$ANDROID_HOME/ndk/$NDK_VER/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/$TRIPLET/35/libcamera2ndk.so" \
./target/x/debug/android/$ARCH/cargo/$TRIPLET/debug/deps
x build --arch $ARCH --platform $PLATFORM --verbose

10
manifest.yaml Normal file
View File

@ -0,0 +1,10 @@
android:
manifest:
package: stream.zap.app
uses_permission:
- name: "android.permission.WRITE_EXTERNAL_STORAGE"
- name: "android.permission.READ_EXTERNAL_STORAGE"
- name: "android.permission.INTERNET"
uses_feature:
- name: "android.hardware.vulkan.level"
required: true

View File

@ -1,9 +1,10 @@
use crate::route::Router; use crate::route::Router;
use eframe::{App, CreationContext, Frame}; use eframe::{App, CreationContext, Frame};
use egui::{Color32, Context, Pos2, Rect, Rounding}; use egui::{Color32, Context};
use nostr_sdk::database::MemoryDatabase; use nostr_sdk::database::MemoryDatabase;
use nostr_sdk::{Client, RelayPoolNotification}; use nostr_sdk::{Client, RelayPoolNotification};
use nostrdb::{Config, Ndb}; use nostrdb::{Config, Ndb};
use std::path::PathBuf;
use tokio::sync::broadcast; use tokio::sync::broadcast;
pub struct ZapStreamApp { pub struct ZapStreamApp {
@ -13,7 +14,7 @@ pub struct ZapStreamApp {
} }
impl ZapStreamApp { impl ZapStreamApp {
pub fn new(cc: &CreationContext) -> Self { pub fn new(cc: &CreationContext, data_path: PathBuf) -> Self {
let client = Client::builder() let client = Client::builder()
.database(MemoryDatabase::with_opts(Default::default())) .database(MemoryDatabase::with_opts(Default::default()))
.build(); .build();
@ -34,12 +35,15 @@ impl ZapStreamApp {
}); });
egui_extras::install_image_loaders(&cc.egui_ctx); egui_extras::install_image_loaders(&cc.egui_ctx);
let ndb = Ndb::new(".", &Config::default()).unwrap(); let ndb_path = data_path.join("ndb");
std::fs::create_dir_all(&ndb_path).expect("Failed to create ndb directory");
let ndb = Ndb::new(ndb_path.to_str().unwrap(), &Config::default()).unwrap();
Self { Self {
client: client.clone(), client: client.clone(),
notifications, notifications,
router: Router::new(cc.egui_ctx.clone(), client.clone(), ndb.clone()), router: Router::new(data_path, cc.egui_ctx.clone(), client.clone(), ndb.clone()),
} }
} }
} }

View File

@ -1,5 +1,6 @@
use eframe::Renderer; use eframe::Renderer;
use egui::Vec2; use egui::Vec2;
use std::path::PathBuf;
use zap_stream_app::app::ZapStreamApp; use zap_stream_app::app::ZapStreamApp;
#[tokio::main] #[tokio::main]
@ -12,9 +13,10 @@ async fn main() {
options.renderer = Renderer::Glow; options.renderer = Renderer::Glow;
options.viewport = options.viewport.with_inner_size(Vec2::new(360., 720.)); options.viewport = options.viewport.with_inner_size(Vec2::new(360., 720.));
let data_path = PathBuf::from(".");
let _res = eframe::run_native( let _res = eframe::run_native(
"zap.stream", "zap.stream",
options, options,
Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc)))), Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, data_path)))),
); );
} }

View File

@ -21,7 +21,7 @@ use winit::platform::android::EventLoopBuilderExtAndroid;
#[tokio::main] #[tokio::main]
pub async fn android_main(app: AndroidApp) { pub async fn android_main(app: AndroidApp) {
std::env::set_var("RUST_BACKTRACE", "full"); std::env::set_var("RUST_BACKTRACE", "full");
android_logger::init_once(android_logger::Config::default().with_min_level(log::Level::Info)); android_logger::init_once(android_logger::Config::default().with_max_level(log::LevelFilter::Info));
let mut options = eframe::NativeOptions::default(); let mut options = eframe::NativeOptions::default();
options.renderer = Renderer::Glow; options.renderer = Renderer::Glow;
@ -31,9 +31,14 @@ pub async fn android_main(app: AndroidApp) {
builder.with_android_app(app_clone_for_event_loop); builder.with_android_app(app_clone_for_event_loop);
})); }));
let external_data_path = app
.external_data_path()
.expect("external data path")
.to_path_buf();
let _res = eframe::run_native( let _res = eframe::run_native(
"zap.stream", "zap.stream",
options, options,
Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc)))), Box::new(move |cc| Ok(Box::new(ZapStreamApp::new(cc, external_data_path)))),
); );
} }

View File

@ -14,6 +14,7 @@ use nostr_sdk::nips::nip01;
use nostr_sdk::{Client, Kind, PublicKey}; use nostr_sdk::{Client, Kind, PublicKey};
use nostrdb::{Filter, Ndb, Note, Transaction}; use nostrdb::{Filter, Ndb, Note, Transaction};
use std::borrow::Borrow; use std::borrow::Borrow;
use std::path::PathBuf;
mod home; mod home;
mod stream; mod stream;
@ -52,7 +53,7 @@ pub struct Router {
} }
impl Router { impl Router {
pub fn new(ctx: Context, client: Client, ndb: Ndb) -> Self { pub fn new(data_path: PathBuf, ctx: Context, client: Client, ndb: Ndb) -> Self {
Self { Self {
current: Routes::HomePage, current: Routes::HomePage,
current_widget: None, current_widget: None,
@ -61,7 +62,7 @@ impl Router {
ndb: NDBWrapper::new(ctx.clone(), ndb.clone(), client.clone()), ndb: NDBWrapper::new(ctx.clone(), ndb.clone(), client.clone()),
client, client,
login: None, login: None,
image_cache: ImageCache::new(ctx.clone()), image_cache: ImageCache::new(data_path, ctx.clone()),
} }
} }

View File

@ -17,8 +17,8 @@ pub struct ImageCache {
} }
impl ImageCache { impl ImageCache {
pub fn new(ctx: egui::Context) -> Self { pub fn new(data_path: PathBuf, ctx: egui::Context) -> Self {
let out = PathBuf::from("./cache/images"); let out = data_path.join("cache/images");
fs::create_dir_all(&out).unwrap(); fs::create_dir_all(&out).unwrap();
Self { Self {
ctx, ctx,

View File

@ -1,18 +1,21 @@
use crate::widgets::VideoPlaceholder; use crate::widgets::VideoPlaceholder;
use egui::{Context, Response, Ui, Vec2, Widget}; use egui::{Context, Response, Ui, Vec2, Widget};
use egui_video::Player; use egui_video::{AudioDevice, Player};
pub struct StreamPlayer { pub struct StreamPlayer {
player: Option<Player>, player: Option<Player>,
audio: AudioDevice,
} }
impl StreamPlayer { impl StreamPlayer {
pub fn new(ctx: &Context, url: &String) -> Self { pub fn new(ctx: &Context, url: &String) -> Self {
let mut audio = AudioDevice::new().unwrap();
Self { Self {
player: Player::new(ctx, url).map_or(None, |mut f| { player: Player::new(ctx, url).map_or(None, |mut f| {
f.start(); f.start();
Some(f) Some(f)
}), }),
audio,
} }
} }
} }
@ -24,6 +27,7 @@ impl Widget for &mut StreamPlayer {
let size = Vec2::new(w, h); let size = Vec2::new(w, h);
if let Some(mut p) = self.player.as_mut() { if let Some(mut p) = self.player.as_mut() {
p.add_audio(&mut self.audio).expect("Failed to add audio to stream player");
p.ui(ui, size) p.ui(ui, size)
} else { } else {
VideoPlaceholder.ui(ui) VideoPlaceholder.ui(ui)