mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-11 17:29:26 +02:00
feat(ios): support iPhone/iPad avec build d'un IPA sideloadable
Le projet était Android-only : le dossier ios/ avait été supprimé et aucune cible Apple n'existait. Le code Dart, lui, est resté portable (aucun appel spécifique à Android) et media_kit_libs_video embarque déjà libmpv pour iOS. - scripts/prepare_ios.sh régénère le projet Xcode via `flutter create` puis applique les réglages nécessaires à une app IPTV : exception App Transport Security (les serveurs Xtream sont en http:// simple), audio en arrière-plan, orientations paysage et 4 sens sur iPad, autorisation réseau local, cible iOS 13 exigée par media_kit et bundle id aligné sur l'applicationId Android. Le script est idempotent et respecte un dossier ios/ déjà versionné. - Nouveau workflow Build iOS : runner macOS, build --no-codesign, empaquetage en IPA non signé publié sur la Release de la version, à côté de l'APK. Pas de déclenchement sur les PR, un runner macOS coûtant 10x en minutes. - IOS_GUIDE.md documente les voies d'installation hors App Store (AltStore / SideStore, Sideloadly, compte développeur, TestFlight) et leurs limites. - flutter_launcher_icons : image_path pointait sur app-icon.png, absent du dépôt ; correction vers le logo existant et ajout de la génération iOS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WoXgHivbAphDk9ArgKDg8Q
This commit is contained in:
7 files changed
+356
-5
No files matched your search
@@ -0,0 +1,76 @@
|
||||
# Construit un IPA *non signé* pour iPhone / iPad et le publie sur la GitHub
|
||||
# Release de la version courante, à côté de l'APK Android.
|
||||
#
|
||||
# L'IPA n'est volontairement pas signé : la signature est faite au moment de
|
||||
# l'installation par AltStore / SideStore / Sideloadly avec l'Apple ID de
|
||||
# l'utilisateur (voir IOS_GUIDE.md). Aucun compte développeur n'est donc
|
||||
# nécessaire pour produire ce fichier.
|
||||
#
|
||||
# Le job tourne sur un runner macOS (Xcode obligatoire) et consomme 10x plus
|
||||
# de minutes GitHub Actions qu'un runner Linux : il n'est donc pas déclenché
|
||||
# sur les pull requests.
|
||||
name: Build iOS
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: stable
|
||||
flutter-version: 3.35.3
|
||||
cache: true
|
||||
|
||||
- name: Read app version from pubspec.yaml
|
||||
id: version
|
||||
run: echo "version=$(grep '^version:' pubspec.yaml | awk '{print $2}' | cut -d+ -f1)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- run: flutter pub get
|
||||
|
||||
# Régénère ios/ (absent du dépôt) et applique les réglages IPTV :
|
||||
# ATS, audio en arrière-plan, orientations iPad, bundle id.
|
||||
- name: Prepare iOS project
|
||||
run: ./scripts/prepare_ios.sh
|
||||
|
||||
- name: Generate iOS app icon
|
||||
run: dart run flutter_launcher_icons -f flutter_launcher_icons-ios.yaml
|
||||
|
||||
- name: Build release app (unsigned)
|
||||
run: flutter build ios --release --no-codesign
|
||||
|
||||
# Un IPA n'est qu'une archive zip contenant Payload/<App>.app.
|
||||
- name: Package unsigned IPA
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
cd build/ios/iphoneos
|
||||
rm -rf Payload
|
||||
mkdir Payload
|
||||
cp -R Runner.app Payload/
|
||||
zip -qry "$GITHUB_WORKSPACE/xtremobile-v${VERSION}-unsigned.ipa" Payload
|
||||
|
||||
- name: Upload IPA artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: xtremobile-v${{ steps.version.outputs.version }}-ipa
|
||||
path: xtremobile-v${{ steps.version.outputs.version }}-unsigned.ipa
|
||||
|
||||
# Même tag que le workflow APK : l'IPA est ajouté à la release existante.
|
||||
- name: Publish GitHub Release
|
||||
if: github.ref == 'refs/heads/main'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ steps.version.outputs.version }}
|
||||
name: XtremMobile v${{ steps.version.outputs.version }}
|
||||
files: xtremobile-v${{ steps.version.outputs.version }}-unsigned.ipa
|
||||
generate_release_notes: true
|
||||
+13
@@ -32,6 +32,19 @@ migrate_working_dir/
|
||||
|
||||
# Web related
|
||||
|
||||
# iOS related (le projet Xcode est régénéré par scripts/prepare_ios.sh)
|
||||
**/ios/Flutter/App.framework
|
||||
**/ios/Flutter/Flutter.framework
|
||||
**/ios/Flutter/Flutter.podspec
|
||||
**/ios/Flutter/Generated.xcconfig
|
||||
**/ios/Flutter/ephemeral/
|
||||
**/ios/Flutter/flutter_export_environment.sh
|
||||
**/ios/Runner/GeneratedPluginRegistrant.*
|
||||
**/ios/Pods/
|
||||
**/ios/Podfile.lock
|
||||
**/ios/.symlinks/
|
||||
*.ipa
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
# Version iPhone / iPad — build et installation hors App Store
|
||||
|
||||
Ce guide explique comment produire XtremFlow pour iOS/iPadOS et l'installer sur
|
||||
un appareil **sans passer par l'App Store**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ce qui a été mis en place
|
||||
|
||||
Le dossier `ios/` n'est pas versionné : il est **régénéré au moment du build**
|
||||
par `scripts/prepare_ios.sh`, ce qui garantit qu'il correspond toujours à la
|
||||
version de Flutter utilisée. Le script applique ensuite les réglages
|
||||
indispensables à une app IPTV :
|
||||
|
||||
| Réglage | Pourquoi |
|
||||
|---|---|
|
||||
| `NSAllowsArbitraryLoads` | Les serveurs Xtream Codes sont presque toujours en `http://`. Sans cette exception, App Transport Security bloque **toutes** les requêtes API et tous les flux. |
|
||||
| `UIBackgroundModes: audio` | Le son continue quand l'écran est verrouillé. |
|
||||
| Orientations paysage + 4 sens sur iPad | Lecteur vidéo plein écran, rotation libre sur tablette. |
|
||||
| `NSLocalNetworkUsageDescription` | Autorisation iOS 14+ si le serveur IPTV est sur le réseau local. |
|
||||
| Cible iOS 13.0 | Minimum exigé par `media_kit` (libmpv/FFmpeg). |
|
||||
| Bundle id `com.xtremflow.mobile` | Aligné sur l'`applicationId` Android. |
|
||||
|
||||
Le code Dart est déjà entièrement portable (aucun appel spécifique Android), et
|
||||
`media_kit_libs_video` embarque déjà la variante iOS de libmpv : les codecs
|
||||
AC3 / EAC3 / DTS restent donc gérés comme sur Android.
|
||||
|
||||
## 2. Produire l'IPA
|
||||
|
||||
### Option A — GitHub Actions (aucun Mac nécessaire) ✅ recommandé
|
||||
|
||||
Le workflow `.github/workflows/build-ios.yml` tourne sur un runner macOS et
|
||||
publie un **IPA non signé** :
|
||||
|
||||
* automatiquement à chaque push sur `main`, joint à la GitHub Release de la
|
||||
version (à côté de l'APK) ;
|
||||
* à la demande via **Actions → Build iOS → Run workflow**, le fichier est alors
|
||||
disponible en artifact.
|
||||
|
||||
> ⚠️ Les runners macOS comptent **10× plus** de minutes GitHub Actions que les
|
||||
> runners Linux. C'est pourquoi le workflow ne se déclenche pas sur les pull
|
||||
> requests.
|
||||
|
||||
### Option B — En local, sur un Mac
|
||||
|
||||
```bash
|
||||
flutter pub get
|
||||
./scripts/prepare_ios.sh
|
||||
dart run flutter_launcher_icons -f flutter_launcher_icons-ios.yaml
|
||||
flutter build ios --release --no-codesign
|
||||
|
||||
cd build/ios/iphoneos
|
||||
mkdir -p Payload && cp -R Runner.app Payload/
|
||||
zip -qry ../../../xtremflow-unsigned.ipa Payload
|
||||
```
|
||||
|
||||
Un IPA n'est rien d'autre qu'une archive zip contenant `Payload/Runner.app`.
|
||||
|
||||
## 3. Installer sans l'App Store
|
||||
|
||||
C'est ici que se situe la vraie contrainte : elle ne vient pas du code mais
|
||||
d'Apple. Toute app iOS doit être **signée** par un certificat Apple avant de
|
||||
pouvoir se lancer. Quatre voies possibles :
|
||||
|
||||
| Méthode | Coût | Validité | Matériel requis |
|
||||
|---|---|---|---|
|
||||
| **AltStore / SideStore** (Apple ID gratuit) | 0 € | **7 jours**, renouvelés automatiquement en Wi-Fi | Un PC/Mac pour l'installation initiale |
|
||||
| **Sideloadly** (Apple ID gratuit) | 0 € | 7 jours, renouvellement manuel | Windows ou macOS, câble USB |
|
||||
| **Compte Apple Developer** | 99 €/an | **1 an** | Idem, mais plus de limites gênantes |
|
||||
| **TestFlight** | 99 €/an | 90 jours par build | Passe par App Store Connect (revue allégée) |
|
||||
|
||||
Limites du compte gratuit : **3 apps sideloadées maximum** et **10 App IDs par
|
||||
semaine**. Avec un compte payant, la signature tient un an et les limites
|
||||
disparaissent — c'est la meilleure option si l'app est utilisée au quotidien.
|
||||
|
||||
### Marche à suivre avec AltStore (gratuit, la plus courante)
|
||||
|
||||
1. Installer **AltServer** sur un PC Windows ou un Mac (altstore.io), plus
|
||||
iTunes + iCloud (versions du site Apple, pas du Microsoft Store) sur Windows.
|
||||
2. Brancher l'iPhone/iPad en USB, faire confiance à l'ordinateur.
|
||||
3. Dans AltServer : *Install AltStore* → choisir l'appareil → saisir son Apple ID.
|
||||
4. Sur l'appareil : **Réglages → Général → VPN et gestion de l'appareil** →
|
||||
faire confiance au profil développeur.
|
||||
5. Télécharger `xtremobile-vX.Y.Z-unsigned.ipa` depuis la GitHub Release.
|
||||
6. Dans AltStore sur l'appareil : **My Apps → + → sélectionner l'IPA**. AltStore
|
||||
signe l'app avec l'Apple ID et l'installe.
|
||||
7. Garder AltServer allumé sur le même Wi-Fi : AltStore renouvelle la signature
|
||||
avant l'expiration des 7 jours. **SideStore** fait la même chose sans PC une
|
||||
fois configuré.
|
||||
|
||||
### Avec Sideloadly
|
||||
|
||||
Plus simple mais sans renouvellement automatique : installer Sideloadly, brancher
|
||||
l'appareil, glisser l'IPA, saisir l'Apple ID, cliquer sur *Start*. À refaire tous
|
||||
les 7 jours.
|
||||
|
||||
### Avec un compte Apple Developer (99 €/an)
|
||||
|
||||
La signature tient **un an** et l'app cesse d'expirer toutes les semaines. Deux
|
||||
approches :
|
||||
|
||||
* signer l'IPA avec Sideloadly/AltStore en utilisant l'Apple ID du compte payant
|
||||
(le plus simple, rien à changer dans le projet) ;
|
||||
* ou passer à une distribution **Ad Hoc** (jusqu'à 100 appareils enregistrés par
|
||||
leur UDID) : il faut alors ajouter un certificat et un profil de provisioning
|
||||
dans le workflow et remplacer `--no-codesign` par un `flutter build ipa
|
||||
--export-options-plist`.
|
||||
|
||||
### Cas particulier de l'Union européenne
|
||||
|
||||
Depuis iOS 17.4, l'UE autorise les **magasins alternatifs** (AltStore PAL,
|
||||
notamment). Y publier l'app exige tout de même un compte développeur payant et
|
||||
la notarisation Apple : intéressant pour distribuer à d'autres personnes, inutile
|
||||
pour un usage personnel.
|
||||
|
||||
### Ce qui n'est pas possible
|
||||
|
||||
Il n'existe **aucun** moyen d'installer une app iOS sans signature Apple sur un
|
||||
appareil non jailbreaké. Un simple lien de téléchargement, comme pour l'APK
|
||||
Android, n'existe pas sur iOS.
|
||||
|
||||
## 4. Points de vigilance
|
||||
|
||||
* **Mises à jour** : pas de mise à jour automatique. Il faut retélécharger l'IPA
|
||||
de la nouvelle release et refaire l'installation.
|
||||
* **Expiration** : une app signée avec un Apple ID gratuit s'ouvre encore après
|
||||
7 jours mais refuse de se lancer ; une réinstallation conserve les données.
|
||||
* **Personnalisation** : pour changer le nom affiché, le bundle id ou la cible
|
||||
de déploiement, modifier les variables en tête de `scripts/prepare_ios.sh` —
|
||||
ne pas éditer `ios/` à la main, il est régénéré à chaque build.
|
||||
* **Projet Xcode versionné** : si vous préférez committer le dossier `ios/`
|
||||
(pour le retoucher dans Xcode), générez-le une fois sur un Mac puis committez-le.
|
||||
`scripts/prepare_ios.sh` détecte sa présence, ne le régénère pas et se contente
|
||||
de réappliquer les réglages du tableau §1.
|
||||
@@ -1,6 +1,13 @@
|
||||
# XtremFlow - IPTV Web Application
|
||||
# XtremFlow - IPTV Application
|
||||
|
||||
High-performance, containerized IPTV Web Application using Flutter Web and Xtream Codes API.
|
||||
High-performance IPTV application built with Flutter and the Xtream Codes API,
|
||||
running on Android, iPhone and iPad.
|
||||
|
||||
## Installation
|
||||
|
||||
- **Android** : télécharger l'APK depuis la [dernière release](../../releases) et l'installer.
|
||||
- **iPhone / iPad** : un IPA non signé est publié sur la même release ; voir
|
||||
[`IOS_GUIDE.md`](IOS_GUIDE.md) pour l'installer sans passer par l'App Store.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Icônes iOS uniquement : le dossier ios/ étant régénéré à chaque build CI,
|
||||
# l'AppIcon doit être reconstruite après `scripts/prepare_ios.sh`.
|
||||
# Les icônes Android, elles, sont versionnées dans android/app/src/main/res.
|
||||
# Usage : dart run flutter_launcher_icons -f flutter_launcher_icons-ios.yaml
|
||||
flutter_launcher_icons:
|
||||
ios: true
|
||||
android: false
|
||||
image_path: "assets/images/logo_xtremflow.png"
|
||||
# L'App Store et iOS refusent une icône avec canal alpha.
|
||||
remove_alpha_ios: true
|
||||
+6
-3
@@ -1,5 +1,5 @@
|
||||
name: xtremobile
|
||||
description: High-performance IPTV Android Application with Xtream Codes API
|
||||
description: High-performance IPTV application (Android, iPhone, iPad) with Xtream Codes API
|
||||
publish_to: 'none'
|
||||
version: 1.7.1+15
|
||||
|
||||
@@ -70,9 +70,12 @@ dev_dependencies:
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: true
|
||||
image_path: "assets/images/app-icon.png"
|
||||
ios: true
|
||||
image_path: "assets/images/logo_xtremflow.png"
|
||||
adaptive_icon_background: "#1a1a2e"
|
||||
adaptive_icon_foreground: "assets/images/app-icon.png"
|
||||
adaptive_icon_foreground: "assets/images/logo_xtremflow.png"
|
||||
# iOS refuse une icône comportant un canal alpha.
|
||||
remove_alpha_ios: true
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||
Executable
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Prépare le dossier ios/ pour un build iPhone / iPad.
|
||||
#
|
||||
# Le dépôt ne versionne pas le projet Xcode : il est régénéré par
|
||||
# `flutter create` au moment du build (macOS uniquement), puis adapté aux
|
||||
# besoins de l'app IPTV (flux HTTP en clair, audio en arrière-plan, iPad).
|
||||
# Si un dossier ios/ est déjà présent dans le dépôt, il est conservé tel quel
|
||||
# et seuls les réglages ci-dessous lui sont réappliqués — le script est
|
||||
# idempotent et peut être relancé sans risque.
|
||||
#
|
||||
# Usage : ./scripts/prepare_ios.sh
|
||||
set -euo pipefail
|
||||
|
||||
BUNDLE_ID="com.xtremflow.mobile" # identique à l'applicationId Android
|
||||
DISPLAY_NAME="XtremFlow"
|
||||
DEPLOYMENT_TARGET="13.0" # minimum exigé par media_kit sur iOS
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ "$(uname -s)" != "Darwin" ]; then
|
||||
echo "::error::Un build iOS ne peut être préparé que sur macOS (Xcode requis)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PBXPROJ="ios/Runner.xcodeproj/project.pbxproj"
|
||||
PLIST="ios/Runner/Info.plist"
|
||||
PODFILE="ios/Podfile"
|
||||
PLISTBUDDY=/usr/libexec/PlistBuddy
|
||||
|
||||
if [ ! -d "ios/Runner.xcodeproj" ]; then
|
||||
echo "==> Aucun dossier ios/ : génération du projet Xcode"
|
||||
# Garde-fou : `flutter create` ne doit recréer que la plateforme iOS. Si une
|
||||
# version de l'outil venait à réécrire le point d'entrée de l'app, on le
|
||||
# restaure — un main.dart écrasé produirait un IPA contenant le compteur
|
||||
# d'exemple de Flutter, sans que le build échoue.
|
||||
MAIN_BACKUP="$(mktemp)"
|
||||
cp lib/main.dart "$MAIN_BACKUP"
|
||||
flutter create --platforms=ios --org com.xtremflow --project-name xtremobile .
|
||||
if ! cmp -s lib/main.dart "$MAIN_BACKUP"; then
|
||||
echo "==> lib/main.dart réécrit par flutter create : restauration"
|
||||
cp "$MAIN_BACKUP" lib/main.dart
|
||||
fi
|
||||
rm -f "$MAIN_BACKUP"
|
||||
# `flutter create --org com.xtremflow` écrit com.xtremflow.xtremobile ; on
|
||||
# aligne sur l'applicationId Android (la cible RunnerTests suit le suffixe).
|
||||
sed -i '' "s/com\.xtremflow\.xtremobile/${BUNDLE_ID}/g" "$PBXPROJ"
|
||||
else
|
||||
echo "==> Dossier ios/ déjà présent : génération ignorée"
|
||||
fi
|
||||
|
||||
echo "==> Cible de déploiement : iOS ${DEPLOYMENT_TARGET}"
|
||||
perl -pi -e "s/IPHONEOS_DEPLOYMENT_TARGET = [0-9.]+;/IPHONEOS_DEPLOYMENT_TARGET = ${DEPLOYMENT_TARGET};/g" "$PBXPROJ"
|
||||
perl -pi -e "s/^#?\s*platform :ios.*/platform :ios, '${DEPLOYMENT_TARGET}'/" "$PODFILE"
|
||||
|
||||
# Les pods (dont libmpv de media_kit) doivent viser la même version qu'ici,
|
||||
# sinon CocoaPods refuse la résolution. Ajouté une seule fois.
|
||||
if ! grep -q "IPHONEOS_DEPLOYMENT_TARGET" "$PODFILE"; then
|
||||
perl -0pi -e "s/(flutter_additional_ios_build_settings\(target\))/\$1\n target.build_configurations.each do |config|\n config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '${DEPLOYMENT_TARGET}'\n end/" "$PODFILE"
|
||||
fi
|
||||
|
||||
echo "==> Info.plist"
|
||||
|
||||
plist_set() { # clé, type, valeur
|
||||
$PLISTBUDDY -c "Set :$1 $3" "$PLIST" 2>/dev/null || $PLISTBUDDY -c "Add :$1 $2 $3" "$PLIST"
|
||||
}
|
||||
plist_reset() { # supprime une clé composite avant de la reconstruire
|
||||
$PLISTBUDDY -c "Delete :$1" "$PLIST" 2>/dev/null || true
|
||||
}
|
||||
|
||||
plist_set CFBundleDisplayName string "$DISPLAY_NAME"
|
||||
plist_set CFBundleName string "$DISPLAY_NAME"
|
||||
|
||||
# Les serveurs Xtream Codes sont très souvent en http:// simple : sans cette
|
||||
# exception, App Transport Security bloque toutes les requêtes et tous les
|
||||
# flux vidéo, sans message d'erreur exploitable côté Dart.
|
||||
plist_reset NSAppTransportSecurity
|
||||
$PLISTBUDDY -c "Add :NSAppTransportSecurity dict" "$PLIST"
|
||||
$PLISTBUDDY -c "Add :NSAppTransportSecurity:NSAllowsArbitraryLoads bool true" "$PLIST"
|
||||
|
||||
# Lecture audio quand l'écran est verrouillé / l'app en arrière-plan.
|
||||
plist_reset UIBackgroundModes
|
||||
$PLISTBUDDY -c "Add :UIBackgroundModes array" "$PLIST"
|
||||
$PLISTBUDDY -c "Add :UIBackgroundModes:0 string audio" "$PLIST"
|
||||
|
||||
# Un serveur Xtream hébergé sur le réseau local déclenche la demande
|
||||
# d'autorisation « réseau local » d'iOS 14+ ; sans description, elle est refusée.
|
||||
plist_set NSLocalNetworkUsageDescription string "Nécessaire pour joindre un serveur IPTV hébergé sur votre réseau local."
|
||||
|
||||
# Orientations : paysage indispensable pour le lecteur, les quatre sens sur iPad.
|
||||
plist_reset UISupportedInterfaceOrientations
|
||||
$PLISTBUDDY -c "Add :UISupportedInterfaceOrientations array" "$PLIST"
|
||||
i=0
|
||||
for o in UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight; do
|
||||
$PLISTBUDDY -c "Add :UISupportedInterfaceOrientations:$i string $o" "$PLIST"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
plist_reset "UISupportedInterfaceOrientations~ipad"
|
||||
$PLISTBUDDY -c "Add :UISupportedInterfaceOrientations~ipad array" "$PLIST"
|
||||
i=0
|
||||
for o in UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown \
|
||||
UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight; do
|
||||
$PLISTBUDDY -c "Add :UISupportedInterfaceOrientations~ipad:$i string $o" "$PLIST"
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
echo "==> Projet iOS prêt (bundle id : ${BUNDLE_ID})"
|
||||
Reference in new issue
Block a user