diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5f47984c..c431a1099 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,26 @@
# Changelog
-## [1.0.8-rc28] - unstable
+## [1.0.8-rc29] - unstable
+### Added
+
+* [PostIt.Android] Lecture des podcasts directs et replays dans la page
+ video partagee avec le desktop : surface LibVLC native Android et moteur
+ embarque, transport authentifie commun et controles uniques. Liberation
+ du lecteur en arriere-plan ; reouverture du flux au retour dans l'application.
+
+* [PostIt.Android] Emission depuis la camera et le microphone Android
+ (Android 8 minimum), encodage H.264/AAC natif et segmentation MPEG-TS
+ sur images cles. Interface, envoi authentifie, retries et finalisation
+ du replay partages avec le desktop. Service de premier plan avec
+ notification d'arret et maintien de la capture en arriere-plan.
+
+### Changed
+
+* [PostIt] Mise a jour d'Avalonia vers 12.1.4.
+
+
+## [1.0.8-rc28] - unstable
### Added
diff --git a/INSTALL.md b/INSTALL.md
index 62b7c15b6..99c4f04b5 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -140,6 +140,87 @@ reads a replay from its beginning; seeking is not implemented. Native
camera/microphone permissions, drivers and codecs must still be verified
on each target Windows/Linux machine.
+### Android podcasts
+
+The Android APK bundles LibVLC and uses a native Android video surface inside
+the same Avalonia video page as desktop. Catalogue, playback controls and
+authenticated segment downloads are shared; no external browser, public media
+URL or camera/microphone permissions are needed to watch a podcast.
+
+Leaving the app stops playback and cancels downloads. Returning recreates the
+player: a replay starts from the beginning, and a live stream rejoins near the
+live edge. Seeking and background audio are not supported.
+
+Broadcasting requires **Android 8 (API 26) or newer**. In the shared podcast
+page, detect devices, select front/rear camera and optionally the microphone,
+then start the live broadcast while PostIt is visible. Camera permission,
+microphone permission when selected, and notification permission on Android 13+
+must be granted. A permission denial is reported in the page without starting
+capture or creating a server broadcast. Permissions are requested before the
+server session is created so a long permission dialog cannot expire it.
+Watching does not request these permissions.
+
+Android uses Camera2 and its native MediaRecorder H.264/AAC encoder, up to 480p at 30 fps,
+not the desktop LibVLC camera inputs or software encoders. MPEG-TS is segmented
+at IDR frames, with timestamps used for segment durations. Closed segments
+enter the same ordered upload/retry/quota/replay pipeline as desktop.
+Devices without a supported camera/encoder report a startup error.
+
+A camera/microphone foreground service and a partial wake lock maintain the
+broadcast when PostIt is backgrounded or the screen locks. Its ongoing
+notification returns to the app and offers **Arreter et publier**. Stopping
+flushes the final segment before completing the replay. Leaving the podcast
+page or logging out still stops capture. Android's explicit service stop,
+process termination or application restart does not automatically restart a
+camera; segments already received by the server remain available under the
+interrupted-replay rules above. Device power policies can impose additional
+restrictions and must be tested on the intended physical devices.
+
+**Native library limitation:** bundled `VideoLAN.LibVLC.Android` 3.6.5 emits
+`XA0141`: its native libraries are not aligned for 16 KiB memory pages.
+Playback on 16 KiB-page devices and store distribution requiring this alignment
+are not validated/supported by this package choice. A compatible native LibVLC
+release is required before claiming that support; a successful APK build or
+a test on a 4 KiB-page emulator does not establish it.
+
+Build with the Android workload and SDK configured:
+
+```bash
+dotnet build src/PostIt/PostIt.Android/PostIt.Android.csproj
+```
+
+On an Android device, verify replay video/audio, live playback, pause/resume,
+rotation, Back navigation and returning after backgrounding the app. Also
+verify an expired session or interrupted network reports a playback error.
+For broadcasting, test front/rear cameras, microphone enabled/disabled,
+permission denial, screen lock, background capture, notification stop and the
+final replay. No automatic retry of a denied permission or a killed camera
+service is performed.
+
+The Debug APK includes a local native-capture test activity (excluded from
+Release); it does not create a server podcast. On a dedicated emulator, grant
+camera, audio and notification permissions, then run:
+
+```bash
+adb shell am start -W -f 0x18000000 -n fr.pschneider.postit/fr.pschneider.postit.PodcastCaptureTestActivity
+# Wait about 20 seconds for foreground/background recording and final flush.
+adb shell run-as fr.pschneider.postit cat files/podcast-capture-test-result.txt
+# Repeat with the second camera, no audio, and the notification stop action:
+adb shell am start -W -f 0x18000000 -n fr.pschneider.postit/fr.pschneider.postit.PodcastCaptureTestActivity --es microphone false --es camera front --es notificationStop true
+```
+
+The result reports the segment count, the background progress and the private
+recording directory under `files/podcast-capture-tests`. Recordings remain
+there for local decoder inspection and can be removed after the test.
+
+The implementation was checked on an Android 16 x64 emulator with 4 KiB
+pages: camera/microphone capture produced five independently decodable
+H.264/AAC segments across foreground/background operation; the second camera
+without audio produced six decodable H.264 segments, including screen-off
+operation and stop through the notification action. The foreground service
+was gone after both tests. These local tests do not replace an authenticated
+Android-to-server broadcast/replay test or validation on physical devices.
+
## Resources
System resources needed to install.
diff --git a/src/PostIt/Directory.Packages.props b/src/PostIt/Directory.Packages.props
index 3b23fc8fb..a6a656368 100644
--- a/src/PostIt/Directory.Packages.props
+++ b/src/PostIt/Directory.Packages.props
@@ -4,7 +4,7 @@
true
- 12.1.3
+ 12.1.4
@@ -13,11 +13,13 @@
-
+
+
+
diff --git a/src/PostIt/PostIt.Android/MainActivity.cs b/src/PostIt/PostIt.Android/MainActivity.cs
index f8fd5a0fd..4cf0947eb 100644
--- a/src/PostIt/PostIt.Android/MainActivity.cs
+++ b/src/PostIt/PostIt.Android/MainActivity.cs
@@ -25,6 +25,7 @@ public class MainActivity : AvaloniaMainActivity
/// The current MainActivity instance.
///
public static MainActivity? Current { get; private set; }
+ internal static bool IsForeground { get; private set; }
protected override void OnCreate(global::Android.OS.Bundle? savedInstanceState)
{
@@ -36,8 +37,8 @@ public class MainActivity : AvaloniaMainActivity
EmojiCompat.Config config = new FontRequestEmojiCompatConfig(this, fontRequest);
EmojiCompat.Init(config);
PlatformBootstrap.InitPlatform();
- base.OnCreate(savedInstanceState);
Current = this;
+ base.OnCreate(savedInstanceState);
}
///
/// Receives the deep-link Intent fired by the system browser after the
@@ -60,9 +61,34 @@ public class MainActivity : AvaloniaMainActivity
}
- public override void OnRequestPermissionsResult(int requestCode, string[]? permissions, Permission[]? grantResults)
+ protected override void OnStop()
{
- if (PostIt.Android.Services.AndroidCurrentLocationProvider
+ PostIt.Services.Platform.SuspendPlayback();
+ base.OnStop();
+ }
+
+ protected override void OnResume()
+ {
+ base.OnResume();
+ IsForeground = true;
+ }
+
+ protected override void OnPause()
+ {
+ IsForeground = false;
+ base.OnPause();
+ }
+
+ protected override void OnRestart()
+ {
+ base.OnRestart();
+ PostIt.Services.Platform.ResumePlayback();
+ }
+
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ if (PostIt.Android.Services.PodcastPermissions.HandleResult(requestCode, permissions, grantResults)
+ || PostIt.Android.Services.AndroidCurrentLocationProvider
.HandlePermissionResult(requestCode, grantResults))
{
return;
diff --git a/src/PostIt/PostIt.Android/PlatformBootstrap.cs b/src/PostIt/PostIt.Android/PlatformBootstrap.cs
index 5b90267f0..2648b0f94 100644
--- a/src/PostIt/PostIt.Android/PlatformBootstrap.cs
+++ b/src/PostIt/PostIt.Android/PlatformBootstrap.cs
@@ -21,5 +21,12 @@ internal static class PlatformBootstrap
};
Platform.TryGetCurrentLocationAsync = AndroidCurrentLocationProvider.TryGetCurrentLocationAsync;
+ Platform.CreateVideoSurface = player => new AndroidVideoSurface(player);
+ if (System.OperatingSystem.IsAndroidVersionAtLeast(26))
+ {
+ Platform.GetPodcastCaptureDevices = AndroidPodcastCapture.GetDevices;
+ Platform.RequestPodcastPermissionsAsync = AndroidPodcastCapture.RequestPermissionsAsync;
+ Platform.CreatePodcastCaptureAsync = AndroidPodcastCapture.StartAsync;
+ }
}
}
diff --git a/src/PostIt/PostIt.Android/PostIt.Android.csproj b/src/PostIt/PostIt.Android/PostIt.Android.csproj
index 18bfec459..6b60f83b6 100644
--- a/src/PostIt/PostIt.Android/PostIt.Android.csproj
+++ b/src/PostIt/PostIt.Android/PostIt.Android.csproj
@@ -21,12 +21,17 @@
+
+
+
+
+