Light theme default + yt-dlp search fallback

This commit is contained in:
iliasmouhcine
2026-08-22 13:02:32 +01:00
commit 7704cae4a0
282 changed files with 52324 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
+30
View File
@@ -0,0 +1,30 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "d7b523b356d15fb81e7d340bbe52b47f93937323"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
- platform: linux
create_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
base_revision: d7b523b356d15fb81e7d340bbe52b47f93937323
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
+84
View File
@@ -0,0 +1,84 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Fix File Picker Android 18.10.1 ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
if (-not (Test-Path ".\pubspec.yaml")) {
throw "pubspec.yaml introuvable. Lance ce script depuis la racine du projet Flutter."
}
if (-not (Test-Path ".\lib\services\memory_backup_service.dart")) {
throw "lib/services/memory_backup_service.dart introuvable. Extrais d'abord tout le ZIP dans la racine du projet."
}
# Ferme uniquement l'executable Windows de l'application s'il tourne encore.
Get-Process -Name "youtube_personal" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
$pubspecPath = ".\pubspec.yaml"
$backupPath = ".\pubspec.yaml.before_file_picker_12"
if (-not (Test-Path $backupPath)) {
Copy-Item $pubspecPath $backupPath
Write-Host "[OK] Sauvegarde pubspec : $backupPath" -ForegroundColor Green
}
Write-Host "[1/4] Passage a file_picker 12.0.0..." -ForegroundColor Yellow
$content = Get-Content $pubspecPath -Raw
if ($content -match '(?m)^\s*file_picker\s*:\s*.*$') {
$content = [regex]::Replace(
$content,
'(?m)^(\s*)file_picker\s*:\s*.*$',
'${1}file_picker: 12.0.0',
1
)
} else {
$dependencyAnchor = '(?m)^dependencies\s*:\s*$'
if ($content -notmatch $dependencyAnchor) {
throw "Section dependencies introuvable dans pubspec.yaml."
}
$content = [regex]::Replace(
$content,
$dependencyAnchor,
"dependencies:`r`n file_picker: 12.0.0",
1
)
}
Set-Content -Path $pubspecPath -Value $content -Encoding UTF8
Write-Host "[OK] file_picker verrouille sur 12.0.0." -ForegroundColor Green
Write-Host "[2/4] Suppression du registrant Android genere precedent..." -ForegroundColor Yellow
$registrant = ".\android\app\src\main\java\io\flutter\plugins\GeneratedPluginRegistrant.java"
if (Test-Path $registrant) {
Remove-Item $registrant -Force
Write-Host "[OK] GeneratedPluginRegistrant.java supprime. Flutter va le regenerer." -ForegroundColor Green
} else {
Write-Host "[INFO] Aucun registrant precedent a supprimer." -ForegroundColor DarkGray
}
if (Test-Path ".\.flutter-plugins-dependencies") {
Remove-Item ".\.flutter-plugins-dependencies" -Force -ErrorAction SilentlyContinue
}
Write-Host "[3/4] Resolution des dependances..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue."
}
Write-Host "[4/4] Formatage et verification rapide..." -ForegroundColor Yellow
dart format ".\lib\services\memory_backup_service.dart"
if ($LASTEXITCODE -ne 0) {
throw "dart format a echoue."
}
Write-Host ""
Write-Host "[OK] Correctif 18.10.1 applique." -ForegroundColor Green
Write-Host ""
Write-Host "Teste maintenant Android avec :" -ForegroundColor Cyan
Write-Host "flutter run -d f10778d8"
Write-Host ""
Write-Host "Puis APK release avec :" -ForegroundColor Cyan
Write-Host "flutter build apk --release"
Write-Host ""
+103
View File
@@ -0,0 +1,103 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Memoire 18.10.3 / Dependances stables ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
if (-not (Test-Path ".\pubspec.yaml")) {
throw "pubspec.yaml introuvable. Lance ce script depuis la racine du projet Flutter."
}
if (-not (Test-Path ".\lib\services\memory_backup_service.dart")) {
throw "memory_backup_service.dart introuvable. Extrais d'abord tout le ZIP dans la racine du projet."
}
Get-Process -Name "youtube_personal" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
$pubspecPath = ".\pubspec.yaml"
$backupPath = ".\pubspec.yaml.before_memory_18_10_3"
if (-not (Test-Path $backupPath)) {
Copy-Item $pubspecPath $backupPath
Write-Host "[OK] Sauvegarde pubspec : $backupPath" -ForegroundColor Green
}
if (Test-Path ".\pubspec.lock") {
$lockBackup = ".\pubspec.lock.before_memory_18_10_3"
if (-not (Test-Path $lockBackup)) {
Copy-Item ".\pubspec.lock" $lockBackup
Write-Host "[OK] Sauvegarde lock : $lockBackup" -ForegroundColor Green
}
}
$content = Get-Content $pubspecPath -Raw
Write-Host "[1/5] Suppression de file_picker..." -ForegroundColor Yellow
$content = [regex]::Replace($content, '(?m)^\s*file_picker\s*:\s*.*(?:\r?\n)?', '')
function Set-DependencyLine {
param([string]$Name, [string]$Version)
$pattern = "(?m)^(\s*)" + [regex]::Escape($Name) + "\s*:\s*.*$"
if ($script:content -match $pattern) {
$script:content = [regex]::Replace(
$script:content,
$pattern,
'${1}' + $Name + ': ' + $Version,
1
)
} else {
$anchor = '(?m)^dependencies\s*:\s*$'
if ($script:content -notmatch $anchor) {
throw "Section dependencies introuvable dans pubspec.yaml."
}
$script:content = [regex]::Replace(
$script:content,
$anchor,
"dependencies:`r`n $Name`: $Version",
1
)
}
}
Write-Host "[2/5] Versions stables et compatibles..." -ForegroundColor Yellow
Set-DependencyLine "wakelock_plus" "1.5.2"
Set-DependencyLine "package_info_plus" "9.0.1"
Set-DependencyLine "file_selector" "1.1.0"
Set-DependencyLine "file_saver" "0.3.1"
Set-Content -Path $pubspecPath -Value $content -Encoding UTF8
Write-Host "[OK] file_picker supprime" -ForegroundColor Green
Write-Host "[OK] wakelock_plus = 1.5.2" -ForegroundColor Green
Write-Host "[OK] package_info_plus = 9.0.1" -ForegroundColor Green
Write-Host "[OK] file_selector = 1.1.0" -ForegroundColor Green
Write-Host "[OK] file_saver = 0.3.1" -ForegroundColor Green
Write-Host "[3/5] Nettoyage du cache de plugins..." -ForegroundColor Yellow
$registrant = ".\android\app\src\main\java\io\flutter\plugins\GeneratedPluginRegistrant.java"
if (Test-Path $registrant) {
Remove-Item $registrant -Force
Write-Host "[OK] Ancien GeneratedPluginRegistrant.java supprime." -ForegroundColor Green
}
if (Test-Path ".\.flutter-plugins-dependencies") {
Remove-Item ".\.flutter-plugins-dependencies" -Force -ErrorAction SilentlyContinue
}
Write-Host "[4/5] Resolution des dependances..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue. Sauvegarde disponible : $backupPath"
}
Write-Host "[5/5] Formatage du service memoire..." -ForegroundColor Yellow
dart format ".\lib\services\memory_backup_service.dart"
if ($LASTEXITCODE -ne 0) {
throw "dart format a echoue."
}
Write-Host ""
Write-Host "[OK] Correctif Memoire 18.10.3 applique." -ForegroundColor Green
Write-Host ""
Write-Host "Teste maintenant :" -ForegroundColor Cyan
Write-Host "flutter run -d f10778d8"
Write-Host ""
Write-Host "Puis, si le test est bon :" -ForegroundColor Cyan
Write-Host "flutter build apk --release"
Write-Host ""
+104
View File
@@ -0,0 +1,104 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Memoire 18.10.4 / Fix package_info_plus ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
if (-not (Test-Path ".\pubspec.yaml")) {
throw "pubspec.yaml introuvable. Lance ce script depuis la racine du projet Flutter."
}
Get-Process -Name "youtube_personal" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
$pubspecPath = ".\pubspec.yaml"
$backupPath = ".\pubspec.yaml.before_memory_18_10_4"
if (-not (Test-Path $backupPath)) {
Copy-Item $pubspecPath $backupPath
Write-Host "[OK] Sauvegarde pubspec : $backupPath" -ForegroundColor Green
}
if (Test-Path ".\pubspec.lock") {
$lockBackup = ".\pubspec.lock.before_memory_18_10_4"
if (-not (Test-Path $lockBackup)) {
Copy-Item ".\pubspec.lock" $lockBackup
Write-Host "[OK] Sauvegarde lock : $lockBackup" -ForegroundColor Green
}
}
$content = Get-Content $pubspecPath -Raw
function Set-DependencyLine {
param([string]$Name, [string]$Version)
$pattern = "(?m)^(\s*)" + [regex]::Escape($Name) + "\s*:\s*.*$"
if ($script:content -match $pattern) {
$script:content = [regex]::Replace(
$script:content,
$pattern,
'${1}' + $Name + ': ' + $Version,
1
)
} else {
$anchor = '(?m)^dependencies\s*:\s*$'
if ($script:content -notmatch $anchor) {
throw "Section dependencies introuvable dans pubspec.yaml."
}
$script:content = [regex]::Replace(
$script:content,
$anchor,
"dependencies:`r`n $Name`: $Version",
1
)
}
}
Write-Host "[1/4] Versions Android compatibles..." -ForegroundColor Yellow
Set-DependencyLine "package_info_plus" "8.3.1"
Set-DependencyLine "wakelock_plus" "1.5.2"
Set-DependencyLine "file_selector" "1.1.0"
Set-DependencyLine "file_saver" "0.3.1"
$content = [regex]::Replace($content, '(?m)^\s*file_picker\s*:\s*.*(?:\r?\n)?', '')
Set-Content -Path $pubspecPath -Value $content -Encoding UTF8
Write-Host "[OK] package_info_plus = 8.3.1" -ForegroundColor Green
Write-Host "[OK] wakelock_plus = 1.5.2" -ForegroundColor Green
Write-Host "[OK] file_selector = 1.1.0" -ForegroundColor Green
Write-Host "[OK] file_saver = 0.3.1" -ForegroundColor Green
Write-Host "[OK] file_picker absent" -ForegroundColor Green
Write-Host "[2/4] Nettoyage des fichiers generes Flutter..." -ForegroundColor Yellow
$registrant = ".\android\app\src\main\java\io\flutter\plugins\GeneratedPluginRegistrant.java"
if (Test-Path $registrant) {
Remove-Item $registrant -Force -ErrorAction SilentlyContinue
Write-Host "[OK] GeneratedPluginRegistrant.java supprime; Flutter le regenerera." -ForegroundColor Green
}
foreach ($path in @(".\.flutter-plugins-dependencies", ".\.dart_tool")) {
if (Test-Path $path) {
try {
Remove-Item $path -Recurse -Force -ErrorAction Stop
Write-Host "[OK] Supprime : $path" -ForegroundColor Green
} catch {
Write-Host "[INFO] Impossible de supprimer completement $path; poursuite." -ForegroundColor DarkYellow
}
}
}
Write-Host "[3/4] Resolution des dependances..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue. Sauvegarde disponible : $backupPath"
}
Write-Host "[4/4] Verification des versions resolues..." -ForegroundColor Yellow
$deps = flutter pub deps --style=compact 2>&1
$deps | Select-String -Pattern "package_info_plus|wakelock_plus|file_selector|file_saver|win32" | ForEach-Object { Write-Host $_.Line }
Write-Host ""
Write-Host "[OK] Correctif Memoire 18.10.4 applique." -ForegroundColor Green
Write-Host ""
Write-Host "Teste Android :" -ForegroundColor Cyan
Write-Host "flutter run -d f10778d8"
Write-Host ""
Write-Host "Puis APK release :" -ForegroundColor Cyan
Write-Host "flutter build apk --release"
Write-Host ""
+116
View File
@@ -0,0 +1,116 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Memoire 18.10.5 / Couple de dependances compatible ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
if (-not (Test-Path ".\pubspec.yaml")) {
throw "pubspec.yaml introuvable. Lance ce script depuis la racine du projet Flutter."
}
if (-not (Test-Path ".\lib\services\memory_backup_service.dart")) {
throw "memory_backup_service.dart introuvable. Le patch Memoire 18.10 doit deja etre installe."
}
$pubspecPath = ".\pubspec.yaml"
$backupPath = ".\pubspec.yaml.before_memory_18_10_5"
if (-not (Test-Path $backupPath)) {
Copy-Item $pubspecPath $backupPath
Write-Host "[OK] Sauvegarde pubspec : $backupPath" -ForegroundColor Green
}
if (Test-Path ".\pubspec.lock") {
$lockBackup = ".\pubspec.lock.before_memory_18_10_5"
if (-not (Test-Path $lockBackup)) {
Copy-Item ".\pubspec.lock" $lockBackup
Write-Host "[OK] Sauvegarde lock : $lockBackup" -ForegroundColor Green
}
}
$content = Get-Content $pubspecPath -Raw
function Set-DependencyLine {
param([string]$Name, [string]$Version)
$pattern = "(?m)^(\s*)" + [regex]::Escape($Name) + "\s*:\s*.*$"
if ($script:content -match $pattern) {
$script:content = [regex]::Replace(
$script:content,
$pattern,
'${1}' + $Name + ': ' + $Version,
1
)
} else {
$anchor = '(?m)^dependencies\s*:\s*$'
if ($script:content -notmatch $anchor) {
throw "Section dependencies introuvable dans pubspec.yaml."
}
$script:content = [regex]::Replace(
$script:content,
$anchor,
"dependencies:`r`n $Name`: $Version",
1
)
}
}
Write-Host "[1/4] Fix du couple wakelock/package_info..." -ForegroundColor Yellow
$content = [regex]::Replace($content, '(?m)^\s*file_picker\s*:\s*.*(?:\r?\n)?', '')
Set-DependencyLine "wakelock_plus" "1.3.3"
Set-DependencyLine "package_info_plus" "8.3.1"
Set-DependencyLine "file_selector" "1.1.0"
Set-DependencyLine "file_saver" "0.3.1"
Set-Content -Path $pubspecPath -Value $content -Encoding UTF8
Write-Host "[OK] wakelock_plus = 1.3.3" -ForegroundColor Green
Write-Host "[OK] package_info_plus = 8.3.1" -ForegroundColor Green
Write-Host "[OK] file_selector = 1.1.0" -ForegroundColor Green
Write-Host "[OK] file_saver = 0.3.1" -ForegroundColor Green
Write-Host "[OK] file_picker absent" -ForegroundColor Green
Write-Host "[2/4] Suppression des anciens fichiers de registration Flutter..." -ForegroundColor Yellow
$registrant = ".\android\app\src\main\java\io\flutter\plugins\GeneratedPluginRegistrant.java"
if (Test-Path $registrant) {
Remove-Item $registrant -Force -ErrorAction SilentlyContinue
Write-Host "[OK] GeneratedPluginRegistrant.java supprime." -ForegroundColor Green
}
if (Test-Path ".\.flutter-plugins-dependencies") {
Remove-Item ".\.flutter-plugins-dependencies" -Force -ErrorAction SilentlyContinue
Write-Host "[OK] .flutter-plugins-dependencies supprime." -ForegroundColor Green
}
Write-Host "[3/4] Resolution des dependances..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue. Sauvegarde disponible : $backupPath"
}
Write-Host "[4/4] Verification des versions resolues..." -ForegroundColor Yellow
$deps = flutter pub deps --style=compact 2>&1 | Out-String
if ($LASTEXITCODE -ne 0) {
throw "Impossible de verifier les dependances resolues."
}
$checks = @(
@{ Name = "wakelock_plus"; Version = "1.3.3" },
@{ Name = "package_info_plus"; Version = "8.3.1" },
@{ Name = "file_selector"; Version = "1.1.0" },
@{ Name = "file_saver"; Version = "0.3.1" }
)
foreach ($check in $checks) {
$pattern = "(?m)\b" + [regex]::Escape($check.Name) + "\s+" + [regex]::Escape($check.Version) + "\b"
if ($deps -match $pattern) {
Write-Host "[OK] $($check.Name) $($check.Version) resolu" -ForegroundColor Green
} else {
Write-Host "[ATTENTION] $($check.Name) $($check.Version) non confirme dans flutter pub deps" -ForegroundColor Yellow
}
}
Write-Host ""
Write-Host "[OK] Correctif Memoire 18.10.5 applique." -ForegroundColor Green
Write-Host ""
Write-Host "Teste Android :" -ForegroundColor Cyan
Write-Host "flutter run -d f10778d8"
Write-Host ""
Write-Host "Puis, si le test est bon :" -ForegroundColor Cyan
Write-Host "flutter build apk --release"
Write-Host ""
+193
View File
@@ -0,0 +1,193 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Memoire 18.10.6 / Bridge Kotlin AGP 9 ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
$root = (Get-Location).Path
$pubspec = Join-Path $root "pubspec.yaml"
$gradleProps = Join-Path $root "android\gradle.properties"
$rootGradle = Join-Path $root "android\build.gradle.kts"
foreach ($path in @($pubspec, $gradleProps, $rootGradle)) {
if (-not (Test-Path $path)) {
throw "Fichier introuvable : $path"
}
}
function Backup-Once([string]$Path, [string]$Suffix) {
$backup = "$Path.$Suffix"
if (-not (Test-Path $backup)) {
Copy-Item $Path $backup
Write-Host "[OK] Sauvegarde : $backup" -ForegroundColor Green
}
}
Backup-Once $pubspec "before_memory_18_10_6"
Backup-Once $gradleProps "before_memory_18_10_6"
Backup-Once $rootGradle "before_memory_18_10_6"
if (Test-Path (Join-Path $root "pubspec.lock")) {
Backup-Once (Join-Path $root "pubspec.lock") "before_memory_18_10_6"
}
Write-Host "[1/6] Verification des dependances stables..." -ForegroundColor Yellow
$content = Get-Content $pubspec -Raw
function Set-DependencyLine {
param([string]$Name, [string]$Version)
$pattern = "(?m)^(\s*)" + [regex]::Escape($Name) + "\s*:\s*.*$"
if ($script:content -match $pattern) {
$script:content = [regex]::Replace(
$script:content,
$pattern,
'${1}' + $Name + ': ' + $Version,
1
)
} else {
$anchor = '(?m)^dependencies\s*:\s*$'
if ($script:content -notmatch $anchor) {
throw "Section dependencies introuvable dans pubspec.yaml."
}
$script:content = [regex]::Replace(
$script:content,
$anchor,
"dependencies:`r`n $Name`: $Version",
1
)
}
}
# file_picker reste supprime. On conserve la pile memoire 18.10.3+.
$content = [regex]::Replace($content, '(?m)^\s*file_picker\s*:\s*.*(?:\r?\n)?', '')
Set-DependencyLine "wakelock_plus" "1.3.3"
Set-DependencyLine "package_info_plus" "8.3.1"
Set-DependencyLine "file_selector" "1.1.0"
Set-DependencyLine "file_saver" "0.3.1"
Set-Content -Path $pubspec -Value $content -Encoding UTF8
Write-Host "[OK] wakelock_plus = 1.3.3" -ForegroundColor Green
Write-Host "[OK] package_info_plus = 8.3.1" -ForegroundColor Green
Write-Host "[OK] file_selector = 1.1.0" -ForegroundColor Green
Write-Host "[OK] file_saver = 0.3.1" -ForegroundColor Green
Write-Host "[2/6] Activation explicite du mode Kotlin legacy compatible AGP 9..." -ForegroundColor Yellow
$props = Get-Content $gradleProps -Raw
function Set-GradleProperty {
param([string]$Name, [string]$Value)
$pattern = '(?m)^\s*' + [regex]::Escape($Name) + '\s*=.*$'
$line = "$Name=$Value"
if ($script:props -match $pattern) {
$script:props = [regex]::Replace($script:props, $pattern, $line, 1)
} else {
if (-not $script:props.EndsWith("`n")) {
$script:props += "`r`n"
}
$script:props += "$line`r`n"
}
}
# Flutter 3.44 utilise ce mode comme pont temporaire pour les plugins KGP.
Set-GradleProperty "android.builtInKotlin" "false"
Set-GradleProperty "android.newDsl" "false"
Set-Content -Path $gradleProps -Value $props -Encoding UTF8
Write-Host "[OK] android.builtInKotlin=false" -ForegroundColor Green
Write-Host "[OK] android.newDsl=false" -ForegroundColor Green
Write-Host "[3/6] Ajout du bridge Kotlin aux plugins qui perdaient leurs classes..." -ForegroundColor Yellow
$gradle = Get-Content $rootGradle -Raw
$beginMarker = "// BEGIN YOUTUBE_PERSONAL_AGP9_KOTLIN_BRIDGE_18_10_6"
$endMarker = "// END YOUTUBE_PERSONAL_AGP9_KOTLIN_BRIDGE_18_10_6"
# Retire une ancienne occurrence du meme bridge s'il y en a une.
$markerPattern = '(?ms)^\s*// BEGIN YOUTUBE_PERSONAL_AGP9_KOTLIN_BRIDGE_18_10_6.*?// END YOUTUBE_PERSONAL_AGP9_KOTLIN_BRIDGE_18_10_6\s*'
$gradle = [regex]::Replace($gradle, $markerPattern, '')
$bridge = @'
// BEGIN YOUTUBE_PERSONAL_AGP9_KOTLIN_BRIDGE_18_10_6
// Flutter 3.44 + AGP 9 peut fonctionner temporairement avec
// android.builtInKotlin=false. Certains plugins regardent uniquement la
// version d'AGP et n'appliquent alors plus KGP : leurs sources .kt ne sont
// jamais compilees et GeneratedPluginRegistrant ne trouve plus les classes.
val youtubePersonalLegacyKotlinPlugins = setOf(
"package_info_plus",
"wakelock_plus",
)
subprojects {
if (name in youtubePersonalLegacyKotlinPlugins) {
val pluginProject = this
pluginManager.withPlugin("com.android.library") {
if (!pluginProject.pluginManager.hasPlugin("org.jetbrains.kotlin.android")) {
pluginProject.pluginManager.apply("org.jetbrains.kotlin.android")
}
}
}
}
// END YOUTUBE_PERSONAL_AGP9_KOTLIN_BRIDGE_18_10_6
'@
$gradle = $gradle.TrimEnd() + "`r`n" + $bridge + "`r`n"
Set-Content -Path $rootGradle -Value $gradle -Encoding UTF8
Write-Host "[OK] Bridge ajoute dans android/build.gradle.kts" -ForegroundColor Green
Write-Host "[4/6] Arret des processus pouvant verrouiller le build..." -ForegroundColor Yellow
Get-Process youtube_personal -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
$gradlew = Join-Path $root "android\gradlew.bat"
if (Test-Path $gradlew) {
Push-Location (Join-Path $root "android")
try {
& .\gradlew.bat --stop | Out-Host
} catch {
Write-Host "[INFO] Aucun daemon Gradle a arreter." -ForegroundColor DarkGray
}
Pop-Location
}
Write-Host "[5/6] Nettoyage Flutter et resolution des dependances..." -ForegroundColor Yellow
$registrant = Join-Path $root "android\app\src\main\java\io\flutter\plugins\GeneratedPluginRegistrant.java"
if (Test-Path $registrant) {
Remove-Item $registrant -Force -ErrorAction SilentlyContinue
}
# On tente un clean complet; si Windows conserve un verrou, on nettoie les
# fichiers de metadata essentiels et Flutter regenerera le reste.
flutter clean
if ($LASTEXITCODE -ne 0) {
Write-Host "[ATTENTION] flutter clean incomplet; nettoyage manuel des metadonnees." -ForegroundColor Yellow
}
foreach ($path in @(
(Join-Path $root ".dart_tool"),
(Join-Path $root ".flutter-plugins-dependencies")
)) {
if (Test-Path $path) {
Remove-Item $path -Recurse -Force -ErrorAction SilentlyContinue
}
}
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue. Les sauvegardes .before_memory_18_10_6 permettent de revenir en arriere."
}
Write-Host "[6/6] Build Android Debug de verification..." -ForegroundColor Yellow
flutter build apk --debug
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Host "Le bridge a ete applique, mais le build a encore echoue." -ForegroundColor Red
Write-Host "Copie uniquement la nouvelle erreur a partir de 'FAILURE: Build failed'." -ForegroundColor Yellow
exit 1
}
Write-Host ""
Write-Host "[OK] Build Android Debug reussi avec le bridge Kotlin AGP 9." -ForegroundColor Green
Write-Host ""
Write-Host "Tu peux maintenant lancer :" -ForegroundColor Cyan
Write-Host " flutter run -d f10778d8"
Write-Host ""
Write-Host "Puis creer l'APK Release :" -ForegroundColor Cyan
Write-Host " flutter build apk --release"
Write-Host ""
+141
View File
@@ -0,0 +1,141 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Memoire 18.10.7 / Memoire Gradle-D8 ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
$root = (Get-Location).Path
$gradleProps = Join-Path $root "android\gradle.properties"
$rootGradle = Join-Path $root "android\build.gradle.kts"
foreach ($path in @($gradleProps, $rootGradle)) {
if (-not (Test-Path $path)) {
throw "Fichier introuvable : $path"
}
}
# Verifie que le bridge 18.10.6 est bien present avant de continuer.
$gradleText = Get-Content $rootGradle -Raw
if ($gradleText -notmatch 'YOUTUBE_PERSONAL_AGP9_KOTLIN_BRIDGE_18_10_6') {
throw "Le bridge Kotlin 18.10.6 est absent. Applique d'abord le correctif 18.10.6."
}
Write-Host "[OK] Bridge Kotlin 18.10.6 detecte." -ForegroundColor Green
$backup = "$gradleProps.before_memory_18_10_7"
if (-not (Test-Path $backup)) {
Copy-Item $gradleProps $backup
Write-Host "[OK] Sauvegarde : $backup" -ForegroundColor Green
}
# Choisit une heap prudente en fonction de la RAM physique disponible.
$totalGb = 0
try {
$totalGb = [math]::Floor((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB)
} catch {
$totalGb = 16
}
if ($totalGb -ge 24) {
$heap = "8g"
$meta = "1536m"
$kotlinHeap = "2048m"
} elseif ($totalGb -ge 16) {
$heap = "6g"
$meta = "1024m"
$kotlinHeap = "1536m"
} elseif ($totalGb -ge 12) {
$heap = "4g"
$meta = "768m"
$kotlinHeap = "1024m"
} else {
$heap = "3g"
$meta = "512m"
$kotlinHeap = "768m"
}
Write-Host "[INFO] RAM detectee : environ $totalGb Go" -ForegroundColor DarkGray
Write-Host "[INFO] Heap Gradle choisie : $heap" -ForegroundColor DarkGray
$props = Get-Content $gradleProps -Raw
function Set-GradleProperty {
param([string]$Name, [string]$Value)
$pattern = '(?m)^\s*' + [regex]::Escape($Name) + '\s*=.*$'
$line = "$Name=$Value"
if ($script:props -match $pattern) {
$script:props = [regex]::Replace($script:props, $pattern, $line, 1)
} else {
if (-not $script:props.EndsWith("`n")) {
$script:props += "`r`n"
}
$script:props += "$line`r`n"
}
}
Write-Host "[1/5] Configuration memoire Gradle / Kotlin..." -ForegroundColor Yellow
Set-GradleProperty "org.gradle.jvmargs" "-Xmx$heap -XX:MaxMetaspaceSize=$meta -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8"
Set-GradleProperty "kotlin.daemon.jvmargs" "-Xmx$kotlinHeap -Dfile.encoding=UTF-8"
Set-GradleProperty "org.gradle.parallel" "false"
Set-GradleProperty "org.gradle.workers.max" "2"
Set-GradleProperty "org.gradle.daemon" "true"
# Conserve les deux proprietes requises par le bridge 18.10.6.
Set-GradleProperty "android.builtInKotlin" "false"
Set-GradleProperty "android.newDsl" "false"
Set-Content -Path $gradleProps -Value $props -Encoding UTF8
Write-Host "[OK] org.gradle.jvmargs configure sur -Xmx$heap" -ForegroundColor Green
Write-Host "[OK] org.gradle.workers.max=2" -ForegroundColor Green
Write-Host "[OK] org.gradle.parallel=false" -ForegroundColor Green
Write-Host "[2/5] Arret du programme et des anciens daemons Gradle..." -ForegroundColor Yellow
Get-Process youtube_personal -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Push-Location (Join-Path $root "android")
try {
& .\gradlew.bat --stop | Out-Host
} catch {
Write-Host "[INFO] Aucun daemon Gradle actif." -ForegroundColor DarkGray
}
Pop-Location
Write-Host "[3/5] Nettoyage des sorties de build uniquement..." -ForegroundColor Yellow
$buildDir = Join-Path $root "build"
if (Test-Path $buildDir) {
try {
Remove-Item $buildDir -Recurse -Force -ErrorAction Stop
Write-Host "[OK] build supprime." -ForegroundColor Green
} catch {
Write-Host "[ATTENTION] build partiellement verrouille; Gradle reconstruira ce qui est necessaire." -ForegroundColor Yellow
}
}
Write-Host "[4/5] Resolution des dependances..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue."
}
Write-Host "[5/5] Build Debug ARM64 de verification..." -ForegroundColor Yellow
Write-Host " (ARM64 uniquement pour reduire fortement le pic memoire D8/CMake)" -ForegroundColor DarkGray
flutter build apk --debug --target-platform android-arm64
if ($LASTEXITCODE -ne 0) {
Write-Host ""
Write-Host "Le correctif memoire est applique mais le build a encore echoue." -ForegroundColor Red
Write-Host "Copie la nouvelle erreur a partir de 'FAILURE:' ou 'Error:'." -ForegroundColor Yellow
exit 1
}
$apk = Join-Path $root "build\app\outputs\flutter-apk\app-debug.apk"
Write-Host ""
Write-Host "[OK] Build Android Debug ARM64 reussi." -ForegroundColor Green
if (Test-Path $apk) {
Write-Host "APK Debug : $apk" -ForegroundColor Cyan
}
Write-Host ""
Write-Host "Teste maintenant sur le smartphone :" -ForegroundColor Cyan
Write-Host " flutter run -d f10778d8"
Write-Host ""
Write-Host "Puis APK Release ARM64 :" -ForegroundColor Cyan
Write-Host " flutter build apk --release --target-platform android-arm64"
Write-Host ""
+110
View File
@@ -0,0 +1,110 @@
param(
[string]$ProjectRoot = (Get-Location).Path
)
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Correctif R8 Release v2 ===" -ForegroundColor Cyan
Write-Host "Projet : $ProjectRoot"
Write-Host ""
$androidApp = Join-Path $ProjectRoot "android\app"
$kts = Join-Path $androidApp "build.gradle.kts"
$groovy = Join-Path $androidApp "build.gradle"
$proguard = Join-Path $androidApp "proguard-rules.pro"
if (-not (Test-Path $androidApp)) {
throw "Dossier Android introuvable : $androidApp"
}
# 1) Cree/complete proguard-rules.pro
$rule = '-dontwarn java.beans.**'
if (-not (Test-Path $proguard)) {
Set-Content -Path $proguard -Value $rule -Encoding UTF8
Write-Host "[OK] proguard-rules.pro cree." -ForegroundColor Green
} else {
$ptext = Get-Content $proguard -Raw
if ($ptext -notmatch '(?m)^\s*-dontwarn\s+java\.beans\.\*\*\s*$') {
Add-Content -Path $proguard -Value "`r`n# Rhino / NewPipeExtractor`r`n$rule" -Encoding UTF8
Write-Host "[OK] Regle java.beans ajoutee." -ForegroundColor Green
} else {
Write-Host "[OK] Regle java.beans deja presente." -ForegroundColor Green
}
}
function Backup-Once([string]$Path) {
$backup = "$Path.before_r8_fix_v2"
if (-not (Test-Path $backup)) {
Copy-Item $Path $backup
Write-Host "[OK] Sauvegarde : $backup" -ForegroundColor DarkGray
}
}
# 2) Branche proguard-rules.pro dans le build type release.
# Contrairement a la v1, ce script ne suppose PAS la presence de
# isMinifyEnabled = true. Flutter/AGP peut lancer R8 sans cette ligne
# explicite dans le fichier utilisateur.
if (Test-Path $kts) {
Backup-Once $kts
$text = Get-Content $kts -Raw
if ($text -match 'proguard-rules\.pro') {
Write-Host "[OK] build.gradle.kts utilise deja proguard-rules.pro." -ForegroundColor Green
} else {
$pattern = '(?m)^(\s*)release\s*\{\s*(?://.*)?$'
$m = [regex]::Match($text, $pattern)
if (-not $m.Success) {
throw "Bloc release { introuvable dans android/app/build.gradle.kts. Aucune modification Gradle effectuee."
}
$indent = $m.Groups[1].Value + ' '
$line = $indent + 'proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")'
$replacement = $m.Value + "`r`n" + $line
$rx = [regex]::new($pattern)
$text = $rx.Replace($text, $replacement, 1)
Set-Content -Path $kts -Value $text -Encoding UTF8
Write-Host "[OK] proguard-rules.pro branche dans release (Kotlin DSL)." -ForegroundColor Green
}
}
elseif (Test-Path $groovy) {
Backup-Once $groovy
$text = Get-Content $groovy -Raw
if ($text -match 'proguard-rules\.pro') {
Write-Host "[OK] build.gradle utilise deja proguard-rules.pro." -ForegroundColor Green
} else {
$pattern = '(?m)^(\s*)release\s*\{\s*(?://.*)?$'
$m = [regex]::Match($text, $pattern)
if (-not $m.Success) {
throw "Bloc release { introuvable dans android/app/build.gradle. Aucune modification Gradle effectuee."
}
$indent = $m.Groups[1].Value + ' '
$line = $indent + "proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'"
$replacement = $m.Value + "`r`n" + $line
$rx = [regex]::new($pattern)
$text = $rx.Replace($text, $replacement, 1)
Set-Content -Path $groovy -Value $text -Encoding UTF8
Write-Host "[OK] proguard-rules.pro branche dans release (Groovy)." -ForegroundColor Green
}
}
else {
throw "Aucun android/app/build.gradle.kts ni android/app/build.gradle trouve."
}
Write-Host ""
Write-Host "=== Verification ===" -ForegroundColor Cyan
if (Test-Path $kts) {
Select-String -Path $kts -Pattern 'release\s*\{|proguardFiles|isMinifyEnabled' | ForEach-Object { Write-Host $_.Line }
} elseif (Test-Path $groovy) {
Select-String -Path $groovy -Pattern 'release\s*\{|proguardFiles|minifyEnabled' | ForEach-Object { Write-Host $_.Line }
}
Write-Host ""
Write-Host "Correctif applique." -ForegroundColor Green
Write-Host "Lance maintenant :" -ForegroundColor Yellow
Write-Host " flutter clean"
Write-Host " flutter pub get"
Write-Host " flutter build apk --release"
Write-Host ""
+83
View File
@@ -0,0 +1,83 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Fix Android Wakelock 18.10.2 ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
if (-not (Test-Path ".\pubspec.yaml")) {
throw "pubspec.yaml introuvable. Lance ce script depuis la racine du projet Flutter."
}
# Fermer l'application Windows si elle tourne et peut verrouiller build/.dart_tool.
Get-Process -Name "youtube_personal" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
$pubspecPath = ".\pubspec.yaml"
$backupPath = ".\pubspec.yaml.before_wakelock_18_10_2"
if (-not (Test-Path $backupPath)) {
Copy-Item $pubspecPath $backupPath
Write-Host "[OK] Sauvegarde pubspec : $backupPath" -ForegroundColor Green
}
if (Test-Path ".\pubspec.lock") {
$lockBackup = ".\pubspec.lock.before_wakelock_18_10_2"
if (-not (Test-Path $lockBackup)) {
Copy-Item ".\pubspec.lock" $lockBackup
Write-Host "[OK] Sauvegarde lock : $lockBackup" -ForegroundColor Green
}
}
Write-Host "[1/3] Verrouillage de wakelock_plus sur 1.5.2..." -ForegroundColor Yellow
$content = Get-Content $pubspecPath -Raw
if ($content -match '(?m)^\s*wakelock_plus\s*:\s*.*$') {
$content = [regex]::Replace(
$content,
'(?m)^(\s*)wakelock_plus\s*:\s*.*$',
'${1}wakelock_plus: 1.5.2',
1
)
} else {
$dependencyAnchor = '(?m)^dependencies\s*:\s*$'
if ($content -notmatch $dependencyAnchor) {
throw "Section dependencies introuvable dans pubspec.yaml."
}
$content = [regex]::Replace(
$content,
$dependencyAnchor,
"dependencies:`r`n wakelock_plus: 1.5.2",
1
)
}
Set-Content -Path $pubspecPath -Value $content -Encoding UTF8
Write-Host "[OK] wakelock_plus = 1.5.2" -ForegroundColor Green
Write-Host "[2/3] Resolution des dependances..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue."
}
Write-Host "[3/3] Verification des versions resolues..." -ForegroundColor Yellow
$deps = flutter pub deps --style=compact 2>&1 | Out-String
$wakeline = ($deps -split "`r?`n" | Where-Object { $_ -match 'wakelock_plus\s' } | Select-Object -First 1)
$fileline = ($deps -split "`r?`n" | Where-Object { $_ -match 'file_picker\s' } | Select-Object -First 1)
if ($wakeline) { Write-Host "[INFO] $wakeline" -ForegroundColor DarkGray }
if ($fileline) { Write-Host "[INFO] $fileline" -ForegroundColor DarkGray }
if ($deps -notmatch 'wakelock_plus\s+1\.5\.2') {
throw "wakelock_plus 1.5.2 n'est pas la version resolue. Verifie pubspec.yaml / dependency_overrides."
}
if ($deps -notmatch 'file_picker\s+12\.0\.0') {
Write-Host "[ATTENTION] file_picker 12.0.0 n'apparait pas dans les dependances resolues." -ForegroundColor Yellow
}
Write-Host ""
Write-Host "[OK] Correctif 18.10.2 applique." -ForegroundColor Green
Write-Host ""
Write-Host "Teste Android avec :" -ForegroundColor Cyan
Write-Host "flutter run -d f10778d8"
Write-Host ""
Write-Host "Puis APK release avec :" -ForegroundColor Cyan
Write-Host "flutter build apk --release"
Write-Host ""
+114
View File
@@ -0,0 +1,114 @@
param(
[string]$ProjectRoot = $PSScriptRoot
)
$ErrorActionPreference = "Stop"
$androidRoot = Join-Path $ProjectRoot "android"
$rootGradle = Join-Path $androidRoot "build.gradle.kts"
$appGradle = Join-Path $androidRoot "app\build.gradle.kts"
if (-not (Test-Path $rootGradle)) {
throw "Introuvable: $rootGradle"
}
if (-not (Test-Path $appGradle)) {
throw "Introuvable: $appGradle"
}
function Backup-Once([string]$Path) {
$backup = "$Path.before_hd"
if (-not (Test-Path $backup)) {
Copy-Item $Path $backup
Write-Host "Backup: $backup"
}
}
Backup-Once $rootGradle
Backup-Once $appGradle
$rootText = Get-Content $rootGradle -Raw
if ($rootText -notmatch 'https://jitpack\.io') {
$needle = 'mavenCentral()'
if (-not $rootText.Contains($needle)) {
throw "Impossible de trouver mavenCentral() dans android/build.gradle.kts"
}
$replacement = @'
mavenCentral()
maven { url = uri("https://jitpack.io") }
'@
$rootText = $rootText.Replace(
$needle,
$replacement
)
Set-Content -Path $rootGradle -Value $rootText -Encoding UTF8
Write-Host "JitPack ajoute."
} else {
Write-Host "JitPack deja present."
}
$appText = Get-Content $appGradle -Raw
# Nettoie les blocs kotlin/compilerOptions qui ont pu être ajoutés par
# les premières versions du script HD. Ils ne sont pas nécessaires ici:
# AGP 9 fait hériter le jvmTarget de compileOptions.targetCompatibility.
$kotlinCompilerBlock = '(?ms)[ \t]*kotlin\s*\{\s*compilerOptions\s*\{\s*jvmTarget\s*=\s*org\.jetbrains\.kotlin\.gradle\.dsl\.JvmTarget\.JVM_17\s*\}\s*\}\s*'
$appText = [regex]::Replace(
$appText,
$kotlinCompilerBlock,
''
)
if (
$appText -notmatch 'isCoreLibraryDesugaringEnabled\s*=\s*true'
) {
$targetNeedle =
'targetCompatibility = JavaVersion.VERSION_17'
if (-not $appText.Contains($targetNeedle)) {
throw "Impossible de trouver targetCompatibility Java 17 dans android/app/build.gradle.kts"
}
$targetReplacement = @'
targetCompatibility = JavaVersion.VERSION_17
isCoreLibraryDesugaringEnabled = true
'@
$appText = $appText.Replace(
$targetNeedle,
$targetReplacement
)
Write-Host "Core library desugaring active."
}
if ($appText -notmatch 'NewPipeExtractor:v0\.26\.3') {
$dependencyBlock = @'
dependencies {
implementation("com.github.TeamNewPipe:NewPipeExtractor:v0.26.3")
implementation("com.squareup.okhttp3:okhttp:4.12.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs_nio:2.1.5")
}
'@
$appText = $appText.TrimEnd() + "`r`n" + $dependencyBlock + "`r`n"
Write-Host "Dependances HD ajoutees."
} else {
Write-Host "Dependances HD deja presentes."
}
Set-Content -Path $appGradle -Value $appText -Encoding UTF8
Write-Host ""
Write-Host "Configuration HD Android appliquee."
Write-Host "Lance ensuite:"
Write-Host " flutter clean"
Write-Host " flutter pub get"
Write-Host " flutter run -d f10778d8"
+111
View File
@@ -0,0 +1,111 @@
$ErrorActionPreference = "Stop"
Write-Host ""
Write-Host "=== YouTube Personal - Memoire Export / Import V1 ===" -ForegroundColor Cyan
Write-Host "Projet : $PWD"
Write-Host ""
if (-not (Test-Path ".\pubspec.yaml")) {
throw "pubspec.yaml introuvable. Lance ce script depuis la racine du projet Flutter."
}
$settingsPath = ".\lib\screens\settings_page.dart"
if (-not (Test-Path $settingsPath)) {
throw "lib/screens/settings_page.dart introuvable."
}
$backupPath = "$settingsPath.before_memory_v1"
if (-not (Test-Path $backupPath)) {
Copy-Item $settingsPath $backupPath
Write-Host "[OK] Sauvegarde settings_page : $backupPath" -ForegroundColor Green
} else {
Write-Host "[INFO] Sauvegarde settings_page deja presente." -ForegroundColor DarkGray
}
Write-Host "[1/4] Ajout de file_picker..." -ForegroundColor Yellow
flutter pub add "file_picker:^11.0.3"
if ($LASTEXITCODE -ne 0) {
throw "flutter pub add file_picker a echoue."
}
Write-Host "[2/4] Integration dans Reglages..." -ForegroundColor Yellow
$content = Get-Content $settingsPath -Raw
$importLine = "import '../widgets/memory_transfer_settings.dart';"
if ($content -notmatch [regex]::Escape($importLine)) {
$anchorImport = "import '../services/preferred_language_service.dart';"
if ($content.Contains($anchorImport)) {
$content = $content.Replace(
$anchorImport,
"$anchorImport`r`n$importLine"
)
} else {
$firstFlutterImport = "import 'package:flutter/material.dart';"
if (-not $content.Contains($firstFlutterImport)) {
throw "Impossible de trouver un point d'insertion pour l'import. Le fichier n'a pas ete modifie."
}
$content = $content.Replace(
$firstFlutterImport,
"$firstFlutterImport`r`n`r`n$importLine"
)
}
Write-Host "[OK] Import MemoryTransferSettings ajoute." -ForegroundColor Green
} else {
Write-Host "[OK] Import deja present." -ForegroundColor Green
}
if ($content -notmatch "const MemoryTransferSettings\(\)") {
$marker = @"
SizedBox(height: 14),
_SettingsSection(
title: 'Navigation',
"@
$replacement = @"
SizedBox(height: 14),
const MemoryTransferSettings(),
SizedBox(height: 14),
_SettingsSection(
title: 'Navigation',
"@
if (-not $content.Contains($marker)) {
# Variante CRLF normalisee par Get-Content -Raw / environnements differents.
$regex = "(?ms)(\s*SizedBox\(height:\s*14\),\s*)(_SettingsSection\(\s*title:\s*'Navigation',)"
if ($content -notmatch $regex) {
throw "Section Navigation introuvable. Restauration du fichier d'origine."
}
$content = [regex]::Replace(
$content,
$regex,
"`r`n SizedBox(height: 14),`r`n const MemoryTransferSettings(),`r`n SizedBox(height: 14),`r`n `$2",
1
)
} else {
$content = $content.Replace($marker, $replacement)
}
Write-Host "[OK] Bloc Memoire ajoute dans Reglages." -ForegroundColor Green
} else {
Write-Host "[OK] Bloc Memoire deja present." -ForegroundColor Green
}
Set-Content -Path $settingsPath -Value $content -Encoding UTF8
Write-Host "[3/4] Formatage Dart..." -ForegroundColor Yellow
dart format ".\lib\services\memory_backup_service.dart" ".\lib\widgets\memory_transfer_settings.dart" $settingsPath
if ($LASTEXITCODE -ne 0) {
throw "dart format a echoue."
}
Write-Host "[4/4] Dependances..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) {
throw "flutter pub get a echoue."
}
Write-Host ""
Write-Host "[OK] Memoire Export / Import V1 installee." -ForegroundColor Green
Write-Host ""
Write-Host "Test Windows : flutter run -d windows"
Write-Host "Test Android : flutter run -d <ID_TELEPHONE>"
Write-Host ""
+61
View File
@@ -0,0 +1,61 @@
# YouTube Personal — Baseline protégée 18.7.5
## Statut
Cette version est la référence fonctionnelle validée pour le démarrage vidéo Windows.
Elle ne doit pas être remplacée par une réécriture du pipeline de démarrage sans test comparatif et possibilité de retour arrière.
## Fichiers protégés
- `lib/screens/video_player_page.dart`
- `lib/services/windows_hd_service.dart`
## Comportement à préserver
1. Au clic sur une vidéo sous Windows, tenter d'abord le démarrage rapide 360p muxé.
2. Ne pas bloquer le premier affichage sur le téléchargement complet de la vidéo et de l'audio.
3. En cas d'échec du chemin rapide, conserver un fallback yt-dlp robuste.
4. Si la qualité cible est supérieure à 360p, préparer la HD en arrière-plan puis basculer à la position courante.
5. Si 360p est explicitement sélectionné, ne pas lancer de préparation HD inutile.
6. Une bascule de qualité ne doit pas incrémenter le compteur personnel de vues.
7. Ne pas réintroduire le relais Range `yt-dlp HttpFD` de la 18.7.4 comme chemin principal : il a provoqué des HTTP 403 et des timeouts de 30 s.
## Règles obligatoires pour les évolutions futures
Avant toute modification touchant le lecteur Windows :
1. Conserver cette archive intacte comme point de retour arrière.
2. Faire une modification minimale et localisée.
3. Ne pas réécrire `WindowsHdService` ou le démarrage du `VideoPlayerPage` si l'évolution demandée ne concerne pas directement le pipeline vidéo.
4. Vérifier le diff contre cette baseline avant livraison.
5. Tester au minimum une vidéo non mise en cache.
6. Vérifier que la première image apparaît avant de valider la modification.
7. Vérifier les logs `[HD-WINDOWS]`, `[HD-PROGRESSIVE]` et `[YT-PLAYER]` en cas de changement du pipeline.
8. Si une régression apparaît, restaurer immédiatement cette baseline avant de poursuivre.
9. Toute livraison doit contenir uniquement les fichiers modifiés/ajoutés avec chemins relatifs au projet.
## Régressions connues à ne pas réintroduire
### 18.7.2 / ancien chemin lent
Le lecteur pouvait attendre le téléchargement complet d'environ 20 MiB de vidéo + 10 MiB d'audio avant de commencer la lecture 360p.
### 18.7.4
Le relais Range basé sur `yt-dlp HttpFD` a produit :
```text
HTTP Error 403: Forbidden
0 octet fourni
Timeout 30 secondes
```
Ce chemin ne doit pas redevenir le démarrage principal.
## Restauration
Depuis le dossier extrait de cette baseline :
```powershell
powershell -ExecutionPolicy Bypass -File .\restore_18_7_5.ps1 -ProjectPath "D:\07-Logiciels\23-youtube_personal"
```
Le script crée d'abord une sauvegarde des fichiers actuels avant de remettre les deux fichiers protégés.
+15
View File
@@ -0,0 +1,15 @@
$ErrorActionPreference = "Stop"
$ProjectRoot = $PSScriptRoot
Set-Location $ProjectRoot
Write-Host "=== Build APK Release ===" -ForegroundColor Cyan
flutter clean
flutter pub get
flutter build apk --release
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$apk = Join-Path $ProjectRoot "build\app\outputs\flutter-apk\app-release.apk"
if (Test-Path $apk) {
Write-Host ""
Write-Host "[OK] APK : $apk" -ForegroundColor Green
} else {
throw "Build termine mais APK introuvable : $apk"
}
+164
View File
@@ -0,0 +1,164 @@
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
$ProjectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $ProjectRoot
Write-Host ""
Write-Host "=== YouTube Personal - Creation installateur Windows V2 ===" -ForegroundColor Cyan
Write-Host "Projet : $ProjectRoot"
Write-Host ""
if (-not (Get-Command flutter -ErrorAction SilentlyContinue)) {
throw "Flutter est introuvable dans le PATH. Ouvre le terminal Flutter habituel puis relance ce script."
}
$PubspecPath = Join-Path $ProjectRoot "pubspec.yaml"
if (-not (Test-Path $PubspecPath)) {
throw "pubspec.yaml introuvable. Copie ce pack a la racine de ton projet Flutter."
}
$Pubspec = Get-Content $PubspecPath -Raw
$Version = "1.0.0"
if ($Pubspec -match '(?m)^version:\s*([^\s+]+)(?:\+\S+)?\s*$') {
$Version = $Matches[1].Trim()
}
Write-Host "[INFO] Version : $Version" -ForegroundColor DarkCyan
Write-Host ""
Write-Host "[1/3] Compilation Windows Release..." -ForegroundColor Yellow
flutter pub get
if ($LASTEXITCODE -ne 0) { throw "flutter pub get a echoue." }
flutter build windows --release
if ($LASTEXITCODE -ne 0) { throw "flutter build windows --release a echoue." }
$ReleaseDir = Join-Path $ProjectRoot "build\windows\x64\runner\Release"
$ExePath = Join-Path $ReleaseDir "youtube_personal.exe"
if (-not (Test-Path $ExePath)) {
throw "Executable introuvable apres compilation : $ExePath"
}
Write-Host "[OK] Application Windows : $ExePath" -ForegroundColor Green
function Find-InnoSetupCompiler {
# 1. PATH
$Cmd = Get-Command ISCC.exe -ErrorAction SilentlyContinue
if ($Cmd) { return $Cmd.Source }
# 2. Emplacements standards Windows, y compris Program Files (x86)
$PF64 = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFiles)
$PF86 = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)
$Candidates = @()
if ($PF86) {
$Candidates += (Join-Path $PF86 "Inno Setup 6\ISCC.exe")
$Candidates += (Join-Path $PF86 "Inno Setup 5\ISCC.exe")
}
if ($PF64) {
$Candidates += (Join-Path $PF64 "Inno Setup 6\ISCC.exe")
$Candidates += (Join-Path $PF64 "Inno Setup 5\ISCC.exe")
}
if ($env:LOCALAPPDATA) {
$Candidates += (Join-Path $env:LOCALAPPDATA "Programs\Inno Setup 6\ISCC.exe")
$Candidates += (Join-Path $env:LOCALAPPDATA "Programs\Inno Setup 5\ISCC.exe")
}
foreach ($Path in $Candidates) {
if ($Path -and (Test-Path $Path)) { return $Path }
}
# 3. Registre: installations machine/utilisateur, 32 et 64 bits
$RegistryRoots = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
)
foreach ($Root in $RegistryRoots) {
try {
$Apps = Get-ItemProperty $Root -ErrorAction SilentlyContinue |
Where-Object { $_.DisplayName -like "Inno Setup*" }
foreach ($App in $Apps) {
if ($App.InstallLocation) {
$Path = Join-Path $App.InstallLocation "ISCC.exe"
if (Test-Path $Path) { return $Path }
}
}
} catch { }
}
# 4. Dernier recours: recherche ciblee dans les dossiers Inno Setup uniquement
$Roots = @($PF86, $PF64, (Join-Path $env:LOCALAPPDATA "Programs")) |
Where-Object { $_ -and (Test-Path $_) }
foreach ($Root in $Roots) {
try {
$Dirs = Get-ChildItem -Path $Root -Directory -Filter "Inno Setup*" -ErrorAction SilentlyContinue
foreach ($Dir in $Dirs) {
$Path = Join-Path $Dir.FullName "ISCC.exe"
if (Test-Path $Path) { return $Path }
}
} catch { }
}
return $null
}
Write-Host ""
Write-Host "[2/3] Detection de Inno Setup..." -ForegroundColor Yellow
$ISCC = Find-InnoSetupCompiler
if (-not $ISCC) {
$Winget = Get-Command winget -ErrorAction SilentlyContinue
if ($Winget) {
Write-Host "[INFO] ISCC.exe non trouve. Tentative d'installation/reparation avec winget..."
# Ne pas considerer 'deja installe / aucune mise a jour' comme une erreur fatale.
& winget install --id JRSoftware.InnoSetup -e --accept-package-agreements --accept-source-agreements --silent
$WingetExit = $LASTEXITCODE
Write-Host "[INFO] winget termine avec le code $WingetExit. Nouvelle detection..."
$ISCC = Find-InnoSetupCompiler
}
}
if (-not $ISCC) {
Write-Host ""
Write-Host "Inno Setup semble installe mais ISCC.exe reste introuvable." -ForegroundColor Red
Write-Host "Teste cette commande pour le localiser :" -ForegroundColor Yellow
Write-Host 'Get-ChildItem "C:\Program Files*" -Filter ISCC.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName' -ForegroundColor Cyan
throw "ISCC.exe introuvable."
}
Write-Host "[OK] Inno Setup : $ISCC" -ForegroundColor Green
Write-Host ""
Write-Host "[3/3] Creation du Setup.exe..." -ForegroundColor Yellow
$InstallerDir = Join-Path $ProjectRoot "installer\windows"
$IssFile = Join-Path $InstallerDir "youtube_personal.iss"
$DistDir = Join-Path $ProjectRoot "dist"
New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
if (-not (Test-Path $IssFile)) {
throw "Fichier Inno Setup introuvable : $IssFile"
}
Push-Location $InstallerDir
try {
& $ISCC "/DAppVersion=$Version" "/DSourceDir=$ReleaseDir" "/DOutputDir=$DistDir" $IssFile
if ($LASTEXITCODE -ne 0) { throw "La compilation Inno Setup a echoue avec le code $LASTEXITCODE." }
}
finally {
Pop-Location
}
$SetupPath = Join-Path $DistDir "YouTube_Personal_Setup_$Version.exe"
if (-not (Test-Path $SetupPath)) {
throw "Le Setup n'a pas ete trouve a l'emplacement attendu : $SetupPath"
}
Write-Host ""
Write-Host "=============================================" -ForegroundColor Green
Write-Host " INSTALLATEUR WINDOWS CREE AVEC SUCCES" -ForegroundColor Green
Write-Host "=============================================" -ForegroundColor Green
Write-Host ""
Write-Host $SetupPath -ForegroundColor Cyan
Write-Host ""
Start-Process explorer.exe -ArgumentList $DistDir
+55
View File
@@ -0,0 +1,55 @@
# Correctif écran verrouillé lorsque le PiP est actif
Le log précédent montrait :
```text
[BACKGROUND] lifecycle=AppLifecycleState.hidden pip=true playing=true
[BACKGROUND] lifecycle=AppLifecycleState.paused pip=true playing=true
...
AudioTrack ... stop
```
Le problème était que le code interprétait `pip=true` comme
« la vidéo est toujours visible » et refusait donc d'activer le mode
audio-only.
Or, lors du verrouillage, Android peut conserver l'état PiP pendant que
l'écran s'éteint et que la Surface vidéo disparaît.
## Nouveau comportement
`MainActivity.kt` écoute maintenant directement :
- `ACTION_SCREEN_OFF`
- `ACTION_SCREEN_ON`
Quand `ACTION_SCREEN_OFF` arrive :
1. Flutter reçoit `screenInteractiveChanged=false`.
2. La piste vidéo est désactivée avec `VideoTrack.no()`, même si `pip=true`.
3. Le Player garde l'audio et la MediaSession.
4. Le foreground service reste actif.
Quand l'écran revient :
1. Flutter reçoit `screenInteractiveChanged=true`.
2. Il attend 450 ms que la Surface Android revienne.
3. `VideoTrack.auto()` restaure l'image.
## Test utile
Lance Android en debug, démarre une vidéo, passe éventuellement en PiP,
puis verrouille le téléphone.
Cherche ces lignes :
```text
[BACKGROUND] native screen interactive=false
[BACKGROUND] disable video track
[BACKGROUND] audio-only active
```
Si ensuite Android affiche malgré tout `AudioTrack ... stop`, cela voudra
dire que `media_kit` arrête son moteur audio indépendamment de la piste vidéo.
Dans ce cas, la prochaine étape sera de déplacer la lecture audio de fond
dans un Player séparé géré par le service Android.
+34
View File
@@ -0,0 +1,34 @@
# Correctif écran verrouillé Android
Ce correctif ne change pas le PiP.
Quand Android met l'application en arrière-plan ou verrouille l'écran :
1. la position et l'état lecture/pause sont conservés ;
2. la piste vidéo de `media_kit` est désactivée avec `VideoTrack.no()` ;
3. l'audio du même flux 360p continue dans la MediaSession / foreground service ;
4. au retour dans l'application, `VideoTrack.auto()` restaure l'image.
Cela évite de maintenir le décodage vidéo / Surface Android lorsque l'écran est éteint.
## Test
Lance :
```powershell
flutter run -d f10778d8
```
Puis :
1. démarre une vidéo ;
2. vérifie qu'elle joue ;
3. verrouille le téléphone sans mettre la vidéo en pause ;
4. attends 20 secondes ;
5. le son doit continuer ;
6. déverrouille ;
7. l'image doit revenir à la position audio actuelle.
Les logs utiles commencent par :
`[BACKGROUND]`
+35
View File
@@ -0,0 +1,35 @@
# Étape 10 — PiP Android + lecture écran verrouillé
## Ce qui change
- `audio_service` maintient une MediaSession Android.
- Android dispose donc de contrôles système :
- retour 10 s
- lecture / pause
- avance 10 s
- Ces contrôles sont utilisés par :
- la notification média
- l'écran verrouillé
- les boutons casque / Bluetooth
- le mode Picture-in-Picture Android lorsqu'on touche la fenêtre PiP.
- Le service passe en foreground pendant la lecture afin que le système
ne suspende pas le moteur de lecture quand l'écran s'éteint.
## Premier lancement Android
Android 13+ peut demander l'autorisation d'afficher les notifications.
Accepte-la pour voir les contrôles média dans la notification et sur
l'écran verrouillé.
## Test conseillé
1. Lance une vidéo.
2. Passe en PiP.
3. Touche une fois la fenêtre PiP :
les contrôles système doivent apparaître.
4. Teste pause / lecture.
5. Reviens dans l'application.
6. Lance la vidéo et verrouille le téléphone.
7. Attends 15 à 30 secondes.
8. Vérifie que le son continue et que la position avance.
9. Sur l'écran verrouillé, teste lecture / pause et +/- 10 s.
+65
View File
@@ -0,0 +1,65 @@
# Étape 11 — Player audio séparé pour écran verrouillé
Les logs précédents montrent que le Player vidéo est arrêté AVANT que
`ACTION_SCREEN_OFF` arrive :
- `playing=true`
- `AudioTrack ... stop`
- puis seulement `screenInteractive=false`
Le correctif précédent ne pouvait donc pas empêcher l'arrêt.
## Nouvelle architecture Android
Quand l'écran s'éteint :
1. on conserve l'URL 360p déjà résolue ;
2. on relève la dernière position connue ;
3. `audio_service` démarre un DEUXIÈME `media_kit.Player` ;
4. ce Player n'a aucun `VideoController` ;
5. `PlayerConfiguration(vo: 'null')` désactive la sortie vidéo ;
6. `VideoTrack.no()` désactive explicitement la piste vidéo ;
7. il seek à la position courante et reprend uniquement l'audio ;
8. les contrôles notification / lockscreen pilotent ce Player.
Quand l'écran se rallume :
1. on lit la position réelle du Player audio de fond ;
2. on le détruit ;
3. on seek le Player vidéo à cette position ;
4. on remet la même vitesse ;
5. on reprend la vidéo si l'audio était encore en lecture.
## Test
```powershell
flutter clean
flutter pub get
flutter run -d f10778d8
```
Puis :
1. démarre une vidéo et attends au moins 10 secondes ;
2. verrouille le téléphone ;
3. accepte une très courte coupure de transition lors de ce premier test ;
4. le son doit reprendre et continuer écran éteint ;
5. utilise play/pause et +/-10 s sur l'écran verrouillé ;
6. rallume et déverrouille ;
7. la vidéo doit revenir à la position atteinte par l'audio.
Logs attendus :
```text
[BACKGROUND] start dedicated audio ...
[BACKGROUND-AUDIO] switching to dedicated player ...
[BACKGROUND-AUDIO] dedicated player ready ... playing=true
[BACKGROUND] dedicated audio active
```
Au retour :
```text
[BACKGROUND-AUDIO] returning to video player ...
[BACKGROUND] video restored ...
```
+53
View File
@@ -0,0 +1,53 @@
# Correctif étape 11 — état lecture + seek du Player de fond
Les nouveaux logs montraient deux problèmes précis :
```text
lifecycle=hidden ... playing=true
lifecycle=paused ... playing=true
AudioTrack ... stop
screen interactive=false ... playing=false
start dedicated audio ... recentlyPlaying=false
switching ... shouldPlay=false
dedicated player ready at 0 ms playing=false
```
Donc :
1. Android arrête le Player vidéo AVANT `ACTION_SCREEN_OFF`.
2. Notre code perdait l'information qu'il était en lecture.
3. Le Player audio de fond recevait `shouldPlay=false`.
4. Son `seek` à ~111 s était envoyé trop tôt et restait à 0 s.
## Correctif
### Intention de lecture
Au callback lifecycle `inactive/hidden/paused`, tant que le Player vaut encore
`playing=true`, on mémorise `continueOnLock=true`.
Quand `ACTION_SCREEN_OFF` arrive plus tard, cette valeur est utilisée même si
Android a déjà mis le Player vidéo à `playing=false`.
### Seek du Player audio
Le Player de fond attend désormais que la durée du média soit connue, puis :
1. seek à la position courante ;
2. vérification de la position ;
3. jusqu'à 3 tentatives ;
4. seulement ensuite `play()` ;
5. confirmation que `playing=true`.
## Logs attendus
```text
[BACKGROUND] lifecycle=AppLifecycleState.hidden ... continueOnLock=true
[BACKGROUND] native screen interactive=false ... playing=false
[BACKGROUND] start dedicated audio ... continueOnLock=true
[BACKGROUND-AUDIO] switching ... shouldPlay=true
[BACKGROUND-AUDIO] seek attempt 1 -> ...
[BACKGROUND-AUDIO] seek confirmed ...
[BACKGROUND-AUDIO] dedicated player ready at ... playing=true
[BACKGROUND] dedicated audio active
```
+17
View File
@@ -0,0 +1,17 @@
# Étape 12 — finition lecture écran verrouillé
Cette étape ne change pas l'architecture qui fonctionne déjà.
Elle ajoute deux sécurités :
1. Pendant la lecture écran verrouillé, la position est sauvegardée localement
toutes les 5 secondes. Si Android tue l'application ou si elle est fermée
pendant que l'écran est verrouillé, la reprise reste donc à jour.
2. Au déverrouillage, le Player vidéo ne fait plus un simple `seek()`.
Il utilise la même logique de `seek + confirmation` que la reprise
automatique déjà validée, pour garantir que l'image revient réellement
à la position du Player audio.
Les boutons du lockscreen `-10 s / pause-play / +10 s` continuent à piloter
le Player audio de fond, et un seek fait depuis le lockscreen est maintenant
persisté immédiatement.
+26
View File
@@ -0,0 +1,26 @@
# Correctif pause + verrouillage Android
Le bug venait du délai `recentlyPlaying` de 10 secondes.
Si la vidéo venait d'être en lecture puis était mise en pause juste avant
le verrouillage, le code considérait encore `recentlyPlaying=true` et
démarrait volontairement le Player audio de fond.
Ce correctif supprime complètement cette heuristique.
Nouveau comportement :
- vidéo en lecture au verrouillage -> audio de fond démarre ;
- vidéo déjà en pause au verrouillage -> elle reste en pause ;
- un événement Android `paused=false` tardif ne peut plus écraser un état
`playing=true` capturé juste avant l'arrêt système de l'AudioTrack.
Test :
1. lance une vidéo ;
2. mets-la en pause ;
3. attends 1 seconde ;
4. verrouille le téléphone ;
5. aucun son ne doit démarrer ;
6. déverrouille : la vidéo doit toujours être en pause ;
7. refais le test avec la vidéo en lecture : l'audio doit continuer.
+19
View File
@@ -0,0 +1,19 @@
# Correctif diagnostic 403 YouTube Data API
La capture Google Cloud confirme que YouTube Data API v3 est activée.
L'ancien code transformait TOUS les HTTP 403 en :
"Vérifie que YouTube Data API v3 est activée", ce qui masquait la vraie cause.
Ce correctif lit maintenant le champ `reason` renvoyé par Google, par exemple :
- insufficientPermissions
- subscriptionForbidden
- quotaExceeded
- accessNotConfigured
Le terminal affiche aussi une ligne :
[YT-ACCOUNT][API-ERROR] status=403 reason=... message=...
Aucun jeton OAuth n'est affiché dans les logs.
+26
View File
@@ -0,0 +1,26 @@
# Correctif crash écran OAuth
Le crash `_dependents.isEmpty` venait de la gestion de la boîte de dialogue
OAuth dans `subscriptions_page.dart`.
Avant, le code :
- créait les `TextEditingController` dans la page parente ;
- modifiait le `ValueNotifier` de configuration pendant que le dialogue
était encore monté dans l'Overlay ;
- reconstruisait la page parente ;
- fermait ensuite le dialogue et détruisait immédiatement les contrôleurs.
Cette combinaison pouvait provoquer une désactivation incohérente dans
l'arbre Flutter en debug.
Le correctif :
- utilise maintenant un vrai `StatefulWidget` pour le dialogue ;
- les contrôleurs appartiennent au dialogue et sont détruits dans son
`dispose()` ;
- aucune configuration n'est enregistrée tant que le dialogue n'est pas
complètement fermé ;
- la page est mise à jour seulement après le retour de `showDialog()`.
Aucun changement OAuth, Google Sign-In, lecture, PiP ou arrière-plan.
+21
View File
@@ -0,0 +1,21 @@
# Étape 14 — chaînes abonnements + vidéos récentes
Ajouts :
- champ de recherche dans les abonnements ;
- clic sur une chaîne ;
- nouvelle page avec les 30 vidéos récentes ;
- clic sur une vidéo -> lecteur YouTube Personal existant ;
- durée affichée grâce à `videos.list`.
Pour éviter de gaspiller le quota YouTube Data API, cette étape n'utilise
PAS `search.list`.
Pour une chaîne ouverte, elle fait seulement :
1. `channels.list(part=contentDetails)` pour trouver la playlist `uploads`;
2. `playlistItems.list` pour lire jusqu'à 30 mises en ligne ;
3. `videos.list(part=contentDetails,status)` pour récupérer les durées.
Les données du compte restent lues avec le token OAuth officiel.
Le token OAuth n'est toujours pas transmis au lecteur vidéo.
+20
View File
@@ -0,0 +1,20 @@
# Correctif affichage Android — cartes d'abonnements
Le log Flutter montrait :
- `A RenderFlex overflowed by 21 pixels on the bottom`
- `subscriptions_page.dart`
- contrainte de carte d'environ `145.7 x 98.6`
La carte était trop basse pour contenir à la fois :
avatar + titre + icône/texte "Voir les vidéos".
Le correctif rend la grille responsive :
- Android / largeur < 560 px : hauteur de carte fixe de 178 px ;
- avatar légèrement réduit ;
- padding et espacements réduits ;
- titre et ligne d'action utilisent `Flexible` ;
- desktop conserve l'affichage actuel.
Aucun changement OAuth, API YouTube, lecteur, PiP ou arrière-plan.
+19
View File
@@ -0,0 +1,19 @@
# Correctif Android — liste au lieu de petites cartes
Les nouveaux logs ne montrent plus l'exception `RenderFlex overflow`.
Donc le problème d'origine de débordement est corrigé.
Pour rendre l'affichage Android robuste, cette version n'utilise plus
la grille 2 colonnes sur petit écran.
Sur Android / largeur < 600 px :
- une chaîne = une ligne ;
- avatar à gauche ;
- nom au centre ;
- icône "Voir les vidéos" visible ;
- chevron à droite.
Sur Windows / tablette :
- la grille existante reste inchangée.
Aucun changement OAuth, API YouTube, lecteur, PiP ou arrière-plan.
+17
View File
@@ -0,0 +1,17 @@
# Correctif compilation mobile
Erreur corrigée :
`The getter 'mobile' isn't defined for the type '_SubscriptionsPageState'`
Cause :
une modification précédente avait remplacé par erreur un `SizedBox`
dans `_buildSignedOut()`, une méthode où la variable locale `mobile`
n'existe pas.
Correctif :
- `_buildSignedOut()` revient à `const SizedBox(width: 12)`;
- le `SizedBox(width: mobile ? 10 : 12)` est placé uniquement dans
`_buildSignedIn()`, là où `mobile` est bien défini.
Aucun autre comportement n'est modifié.
+21
View File
@@ -0,0 +1,21 @@
# Correctif overflow Accueil Android
Le dernier log ne montre plus d'erreur dans `subscriptions_page.dart`.
La seule exception Flutter restante est :
`A RenderFlex overflowed by 0.188 pixels on the bottom`
et elle vient de :
`lib/screens/home_page.dart:270`
La carte de l'accueil avait une hauteur mobile de 220 px. Le calcul du
thumbnail 16:9 + textes + espacements aboutissait à une différence
fractionnaire sur cet appareil.
Correctif :
- hauteur mobile de la section horizontale : 220 -> 226 px ;
- desktop reste à 255 px.
Aucun changement dans les abonnements, OAuth, lecteur, PiP ou arrière-plan.
+19
View File
@@ -0,0 +1,19 @@
# Correctif mobile : en-tête et navigation
La vidéo montre que les avatars des chaînes se chargent correctement.
Les deux problèmes visibles sont surtout des problèmes de place sur 411 dp :
- le titre `Abonnements (37)` passe sur deux lignes ;
- le libellé `Abonnements` de la barre de navigation passe aussi sur deux lignes.
Correctif :
- titre forcé sur une seule ligne sur mobile ;
- taille du titre légèrement réduite sur mobile ;
- actions `Configuration OAuth` et `Déconnexion` regroupées dans un menu `⋮` ;
- le bouton `Actualiser` reste directement visible ;
- libellé mobile `Abonnements` raccourci en `Abos` ;
- desktop conserve `Abonnements` dans la NavigationRail.
Aucun changement OAuth, API YouTube, lecteur, PiP ou arrière-plan.
+29
View File
@@ -0,0 +1,29 @@
# Étape 15 — correctif boucle OAuth / quota YouTube
Le nouveau log montre la vraie cause :
`reason=quotaExceeded`
La boucle venait de l'interaction entre deux morceaux du code :
1. `_loadSubscriptions()` appelait `getAccessToken()`.
2. Sur Android, `getAccessToken()` réécrivait systématiquement `session.value`.
3. Le listener `_onSessionChanged()` voyait cette écriture.
4. Il rappelait immédiatement `_loadSubscriptions()`.
5. La boucle recommençait et envoyait des centaines de requêtes à
YouTube Data API jusqu'à épuiser le quota du projet.
Correctif :
- `getAccessToken()` ne notifie plus `session.value` si le token est inchangé ;
- `_onSessionChanged()` ne relance jamais les abonnements si une requête
est déjà en cours ;
- `_loadSubscriptions()` possède maintenant un verrou anti-réentrance ;
- une connexion initiale continue de charger automatiquement les abonnements.
Important :
le quota actuellement épuisé est un quota du projet YouTube Data API,
pas un bannissement du compte Google/YouTube.
Après remplacement des fichiers, redémarre l'application. Le flot de
centaines de lignes `[YT-ACCOUNT][API-ERROR]` doit disparaître.
+27
View File
@@ -0,0 +1,27 @@
# Étape 16.1 - correctif nullable height
Erreur corrigée :
- `_player.state.height` est de type `int?`
- `_player.stream.height` émet également des `int?`
Le code HD comparait ces valeurs directement avec des `int`, ce qui
empêchait la compilation Dart.
Correctif :
- `state.height ?? 0`
- `.whereType<int>()` sur le stream de hauteur
- `state.width ?? 0` pour le diagnostic de résolution
Aucun changement dans l'extraction NewPipe, Gradle, PiP, arrière-plan
ou OAuth.
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d f10778d8
```
Un `flutter clean` n'est pas nécessaire pour ce correctif Dart.
+22
View File
@@ -0,0 +1,22 @@
# Étape 16.1 - correctif Stream<int?>
Erreur corrigée :
`Stream<int?>` ne possède pas la méthode `whereType()` dans cette
configuration Dart.
Le code utilise maintenant directement :
- `firstWhere((height) => (height ?? 0) >= minimumHeight)`
- puis `streamedHeight ?? 0`
Aucun autre changement n'est effectué dans le lecteur HD.
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d f10778d8
```
Pas besoin de `flutter clean` ni de `flutter pub get`.
+53
View File
@@ -0,0 +1,53 @@
# Étape 16.1 - HD directe vidéo + audio séparés
Le log précédent confirme deux choses importantes :
1. NewPipeExtractor arrive bien à voir les qualités HD :
2160p60, 1440p60, 1080p60, 720p60, etc.
2. Le manifest HLS ne devient jamais prêt dans media_kit Android au bout
de 9 secondes, donc le code retourne automatiquement au 360p.
Cette étape n'utilise donc plus HLS comme source principale.
Nouvelle stratégie :
- NewPipeExtractor choisit un flux vidéo-only direct ;
- priorité AVC/H.264 ;
- plafond volontaire à 1080p pour ce premier test ;
- choix d'un flux audio direct séparé ;
- priorité AAC/mp4a ;
- media_kit ouvre la vidéo et charge l'audio via `AudioTrack.uri(...)` ;
- le Player vérifie ensuite la vraie hauteur décodée ;
- si la HD échoue, fallback automatique vers le 360p stable.
Le lecteur écran verrouillé reste sur le flux progressif 360p stable.
## Test
Après extraction à la racine du projet :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d f10778d8
```
Pas besoin de modifier Gradle et ne relance pas les scripts Gradle précédents.
## Logs recherchés
Succès attendu :
```text
[HD] paire directe sélectionnée: 1080p60 ...
[HD] lecture confirmée: 1920x1080 ...
```
Si la paire directe échoue :
```text
[HD] paire directe non lisible, retour 360p: ...
```
L'interface affiche aussi maintenant la qualité réellement confirmée
par media_kit, et pas seulement la qualité annoncée par l'extracteur.
+80
View File
@@ -0,0 +1,80 @@
# Étape 16.2 - Sélecteur qualité + 4K Android
Cette étape part de la version Android 1080p réellement validée.
## Nouveau sélecteur
Le lecteur Android propose maintenant :
- Auto
- 360p
- 720p
- 1080p
- 1440p
- 2160p (4K)
La qualité par défaut reste 1080p, car c'est la qualité déjà validée.
## Changement en cours de lecture
Changer la qualité recharge le flux en conservant :
- la position actuelle ;
- l'état lecture/pause ;
- la vitesse de lecture.
## Codecs
Jusqu'à 1080p :
- priorité AVC/H.264
Pour 1440p et 2160p :
- priorité VP9
- puis AV1
- puis meilleur flux restant
Le son reste une piste séparée, avec priorité AAC.
## Fallback
Pour Auto, 1440p et 2160p :
1. tentative dans la qualité demandée ;
2. si le téléphone ne peut pas la lire, tentative 1080p AVC ;
3. si cela échoue aussi, retour au 360p stable.
Le Player audio écran verrouillé continue d'utiliser le flux progressif
360p stable.
## Windows
Windows reste volontairement en 360p dans cette étape. Le sélecteur
Windows sera ajouté après validation du 4K Android.
## Test
Après extraction à la racine du projet :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d f10778d8
```
Aucun script Gradle à relancer.
Pour tester la 4K, choisir `2160p (4K)`.
Succès attendu :
```text
[HD] paire directe sélectionnée: 2160p...
[HD] lecture confirmée: 3840x2160 ...
```
Si le téléphone refuse le flux 4K :
```text
[HD] cible=2160 non lisible: ...
[HD] essai de secours 1080p AVC
```
+20
View File
@@ -0,0 +1,20 @@
# Correctif Gradle Flutter 3.44 / AGP 9
Erreur observée :
- `kotlin { ... }` interprété au niveau projet ;
- `compilerOptions` et `jvmTarget` non résolus.
Avec le Kotlin intégré d'AGP 9 utilisé par les versions Flutter récentes,
le bloc Kotlin de l'application doit se trouver dans `android { ... }`.
Le script `REPARER_HD_ANDROID_GRADLE.ps1` :
1. sauvegarde `android/app/build.gradle.kts` ;
2. retire le bloc Kotlin invalide au niveau projet ;
3. l'insère dans `android { ... }` ;
4. conserve le desugaring ;
5. conserve les dépendances NewPipeExtractor/OkHttp.
Le fichier `APPLIQUER_HD_ANDROID.ps1` est également remplacé par une version
corrigée pour éviter que le même problème revienne.
+24
View File
@@ -0,0 +1,24 @@
# Étape 16 - correctif Kotlin DSL / AGP 9
Le correctif précédent a déplacé `kotlin { compilerOptions { ... } }`
dans `android {}`, mais ce bloc n'est pas reconnu dans la configuration
actuelle du projet.
Le correctif actuel retire complètement ce bloc.
Pourquoi c'est sûr ici :
- `android.compileOptions.targetCompatibility` est déjà Java 17 ;
- avec le Kotlin intégré d'AGP 9, `jvmTarget` hérite par défaut de
`android.compileOptions.targetCompatibility` ;
- on n'a donc pas besoin de déclarer `jvmTarget` séparément.
Le script conserve :
- `isCoreLibraryDesugaringEnabled = true` ;
- NewPipeExtractor 0.26.3 ;
- OkHttp 4.12.0 ;
- desugar_jdk_libs_nio 2.1.5.
Le fichier `APPLIQUER_HD_ANDROID.ps1` est aussi remplacé pour qu'il ne
réintroduise plus ce bloc lors d'un futur ré-essai de l'étape HD.
+34
View File
@@ -0,0 +1,34 @@
# Étape 16 - correctif MainActivity absente de l'APK
Le build Gradle passe maintenant, mais Android plante immédiatement avec :
`ClassNotFoundException: com.studioweb.youtube_personal.MainActivity`
Cela signifie que le manifeste trouve bien le nom de l'activité, mais que
la classe Kotlin `MainActivity` n'a pas été empaquetée dans l'APK.
Pour ne plus dépendre de la configuration Kotlin du module app pendant
l'expérimentation HD, `MainActivity` est désormais en Java.
Fonctions conservées :
- `AudioServiceActivity`
- PiP Android
- notification permission Android 13+
- détection écran ON/OFF
- callbacks PiP vers Flutter
- canal MethodChannel HD
- extraction HD sur thread dédié
L'ancien `MainActivity.kt` est remplacé par un fichier sans classe afin de
ne pas créer de doublon si Kotlin est réactivé plus tard.
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d f10778d8
```
Pas besoin de relancer les scripts Gradle précédents.
+86
View File
@@ -0,0 +1,86 @@
# Étape 16 - HD Android expérimentale
## Pourquoi cette nouvelle approche
Les essais précédents avec `youtube_explode_dart` ont réellement atteint
le blocage YouTube :
`Sign in to confirm you're not a bot`
et les flux adaptatifs 720p/1080p ne démarraient pas.
En 2026, YouTube applique de plus en plus SABR et les PO Tokens.
NewPipe 0.28.8 a publié un contournement spécifique du problème
"seulement MP4 360p", via NewPipeExtractor 0.26.3.
Cette étape teste une voie différente :
- extraction native Android via NewPipeExtractor 0.26.3 ;
- récupération du manifest HLS Apple/VisionOS ;
- un seul Player `media_kit`, donc pas de synchronisation manuelle
audio + vidéo ;
- fallback automatique vers le 360p stable si HD échoue ;
- Windows reste volontairement en 360p stable pour cette étape.
## Séparation du compte Google
Le code `NewPipeDownloader` n'injecte :
- aucun cookie Google ;
- aucun token OAuth ;
- aucun identifiant du compte Google connecté.
La connexion Google reste exclusivement utilisée pour YouTube Data API
(abonnements). Le chemin de lecture HD reste anonyme.
## Lecture écran verrouillé
Même si l'image visible utilise HLS HD, le Player dédié écran verrouillé
continue d'utiliser l'URL progressive 360p stable. Cela évite de casser
la lecture en arrière-plan déjà validée.
## Installation
Après extraction du ZIP à la racine du projet :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
powershell -ExecutionPolicy Bypass -File .\APPLIQUER_HD_ANDROID.ps1
flutter clean
flutter pub get
flutter run -d f10778d8
```
Le script fait une sauvegarde automatique :
- `android/build.gradle.kts.before_hd`
- `android/app/build.gradle.kts.before_hd`
## Logs attendus
Succès :
```text
[HD] demande native pour ...
[HD] HLS disponible (...)
```
Fallback :
```text
[HD] extraction indisponible, fallback 360p: ...
```
ou :
```text
[HD] HLS non prêt, retour 360p: ...
```
En cas de fallback, le lecteur 360p doit continuer à fonctionner.
## Licence
NewPipeExtractor est sous GPL-3.0-or-later. Pour un test/personnel cela
ne change pas l'installation locale. Si l'application est distribuée
plus tard, il faudra traiter correctement les obligations de licence.
+94
View File
@@ -0,0 +1,94 @@
# Étape 17.10 - Relais HTTP local chunké pour Windows
## Ce que le test yt-dlp vient de prouver
Le test direct `yt-dlp -f 299 --test` réussit réellement :
```text
[info] ... Downloading 1 format(s): 299
[download] Destination: ...
[download] 100% of 10.00KiB
```
Donc :
- le client `mweb` fonctionne ;
- le PO Token est généré ;
- le serveur `googlevideo.com` accepte le flux ;
- l'IP et les headers sont valides ;
- le problème restant est le transport direct libmpv -> googlevideo.
yt-dlp fournit aussi `downloader_options.http_chunk_size` pour YouTube
(typiquement 10 MiB). La documentation yt-dlp signale que YouTube peut
throttler les requêtes HTTP dont les chunks dépassent 10 MiB.
## Nouvelle architecture Windows
```text
yt-dlp
|
| URL vidéo/audio + PO Token + headers + http_chunk_size
v
Relais HTTP local Dart (127.0.0.1 uniquement)
|
| requêtes Range vers googlevideo <= 10 MiB
v
Googlevideo
libmpv/media_kit
|
+----> http://127.0.0.1:<port>/video
+----> http://127.0.0.1:<port>/audio
```
libmpv ne contacte donc plus directement Google.
Le relais :
- écoute uniquement sur `127.0.0.1` ;
- transmet les headers exacts yt-dlp ;
- force `Accept-Encoding: identity` ;
- respecte les requêtes `Range` de mpv ;
- découpe l'upstream en blocs au maximum égaux au chunk yt-dlp ;
- permet le seek ;
- relaie séparément vidéo et audio.
Android n'est pas modifié.
## Installation
Extraire ce ZIP à la racine du projet.
Aucune nouvelle dépendance.
Aucun nouveau script PowerShell.
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Tester 1080p d'abord.
## Logs attendus
```text
[HD-WINDOWS][17.10] yt-dlp -> relais local -> libmpv
[HD-WINDOWS][17.9] extraction mweb + PO Token provider local
[HD-WINDOWS][17.10] transport yt-dlp: ... chunk=10485760o ...
[HD-RELAY][17.10] actif sur 127.0.0.1:...
[HD-RELAY] /video GET bytes=...
```
Succès :
```text
[HD-WINDOWS] vidéo relayée confirmée: 1920x1080
[HD-WINDOWS] lecture confirmée: 1920x1080 (relais local)
```
Si le relais reçoit un refus upstream, le log affichera directement :
```text
[HD-RELAY] /video échec: googlevideo HTTP 403 ...
```
+76
View File
@@ -0,0 +1,76 @@
# Étape 17.11 - Son + stabilité du relais Range Windows
## Diagnostic du log 17.10
La vidéo fonctionne réellement en 1920x1080 :
```text
1920 1080
[HD-WINDOWS] vidéo relayée confirmée: 1920x1080
```
Le son manque parce que la toute première requête audio reçoit :
```text
googlevideo HTTP 403 pour bytes=0-10485759
```
La vidéo finit elle aussi par recevoir un 403 exactement sur des requêtes
de 10 MiB.
## Cause importante
Notre relais 17.10 envoyait systématiquement des chunks de :
```text
10485760 octets = 10 MiB
```
Le downloader HTTP officiel de yt-dlp ne fait pas cela. Lorsqu'un
`http_chunk_size` est configuré, il randomise chaque nouvelle requête entre
95 % et 100 % de la taille cible.
La FAQ yt-dlp avertit également que YouTube peut throttler/refuser des
requêtes dont la taille dépasse 10 MB.
## Correctif 17.11
Le relais :
- plafonne maintenant les requêtes à 9 900 000 octets ;
- randomise chaque Range entre 95 % et 100 % ;
- force une nouvelle connexion logique par Range ;
- en cas de 403/429, retente jusqu'à 4 fois en divisant progressivement le
chunk ;
- flush le downstream après chaque Range pour éviter le gros buffering lors
d'un seek ;
- journalise séparément le client et la présence du `pot=` pour vidéo/audio.
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Tester uniquement le 1080p avec le son.
Logs importants :
```text
[HD-WINDOWS][17.11] GVS: videoClient=MWEB videoPOT=true audioClient=MWEB audioPOT=true
[HD-RELAY][17.11] upstream OK ...
```
Puis :
```text
[HD-WINDOWS] vidéo relayée confirmée: 1920x1080
[HD-WINDOWS] lecture confirmée: 1920x1080
```
Si un 403 survient encore, la 17.11 affichera désormais les retries et la
taille exacte du chunk qui a été refusé.
+101
View File
@@ -0,0 +1,101 @@
# Étape 17.12 - Relais local piloté par le downloader natif yt-dlp
## Ce que le log 17.11 a prouvé
La résolution n'est plus le problème :
```text
[HD-WINDOWS] vidéo relayée confirmée: 1920x1080
```
Les deux URLs sont bien `MWEB` et portent bien un PO Token :
```text
videoClient=MWEB videoPOT=true
audioClient=MWEB audioPOT=true
```
Mais le `HttpClient` Dart reçoit encore des 403 :
- audio : dès l'offset 0, même avec un Range réduit à ~1,18 Mo ;
- vidéo : après environ 31 Mo, puis systématiquement au même offset.
Cela invalide l'hypothèse "chunk trop gros" comme cause principale.
## Différence importante avec yt-dlp
Le downloader HTTP officiel de yt-dlp ne se limite pas à ajouter les mêmes
headers. Il utilise son propre stack réseau et sa propre logique de reprise
Range.
L'étape 17.12 arrête donc d'essayer de reproduire ce transport en Dart.
## Nouvelle architecture
```text
libmpv
|
| HTTP Range
v
127.0.0.1
|
| lance yt-dlp --load-info-json -o -
v
yt-dlp HttpFD
|
| URL MWEB + PO Token + headers + Range
v
googlevideo
```
Pour chaque connexion `/video` ou `/audio`, le relais crée temporairement un
info-json local contenant :
- l'URL signée déjà extraite ;
- les headers exacts ;
- le Range demandé par mpv ;
- le `http_chunk_size` yt-dlp ;
- la taille totale.
Puis il demande au `yt-dlp.exe` déjà installé de télécharger cette plage vers
stdout. Le relais retransmet stdout directement à libmpv.
Ainsi Google n'est plus contacté par le `HttpClient` Dart. Le transport
googlevideo est effectué par le même moteur yt-dlp qui a réussi le test manuel.
## Seek
Lors d'un seek, mpv ferme/ouvre une nouvelle requête Range. Le relais lance un
nouveau process yt-dlp à l'offset demandé. Le process précédent est stoppé
lorsque sa connexion n'est plus utile.
## Installation
Aucune dépendance ou script supplémentaire.
Extraire ce ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Tester 1080p + son.
## Logs attendus
```text
[HD-WINDOWS][17.12] yt-dlp -> relais yt-dlp natif -> libmpv
[HD-RELAY][17.12] actif sur 127.0.0.1:...
[HD-RELAY][17.12] video: yt-dlp démarre Range=...
[HD-RELAY][17.12] audio: yt-dlp démarre Range=...
```
Succès :
```text
[HD-RELAY][17.12] video: yt-dlp OK ...
[HD-RELAY][17.12] audio: yt-dlp OK ...
[HD-WINDOWS] lecture confirmée: 1920x1080
```
+82
View File
@@ -0,0 +1,82 @@
# Étape 17.13 - Windows 1080p avec HLS pré-muxé web_safari
## Diagnostic après le test 17.12
Le test confirme :
- `1920x1080` est réellement décodé ;
- vidéo et audio mweb possèdent tous les deux un PO Token ;
- malgré cela, yt-dlp lui-même reçoit encore `HTTP Error 403` sur les URLs
DASH/GVS mweb ;
- l'audio échoue immédiatement ;
- la vidéo fonctionne environ 30 Mo puis est refusée.
Ce comportement correspond à des problèmes mweb + PO Token actuellement
rapportés côté yt-dlp/YouTube : un format peut être exposé avec un PO Token
valide mais son URL média retourner tout de même HTTP 403.
## Nouveau chemin prioritaire pour 720/1080p
yt-dlp documente `web_safari` comme pouvant exposer des formats HLS
pré-muxés :
```text
vidéo + audio dans UNE seule URL m3u8
```
Le code Windows essaie donc maintenant :
```text
web_safari
↓
HLS muxé 720/1080
↓
media_kit/libmpv
```
Cela supprime complètement :
- le flux audio DASH séparé ;
- `AudioTrack.uri()` ;
- le relais audio ;
- le GVS PO Token mweb pour le chemin 1080p.
Si YouTube ne fournit pas de HLS web_safari pour la session courante, le code
retombe automatiquement sur le pipeline mweb existant afin que le diagnostic
reste fonctionnel.
## Limitation actuelle
Le HLS `web_safari` est destiné à stabiliser 720/1080p. Il ne constitue pas
notre chemin 1440p/4K.
Depuis juillet 2026, YouTube ne renvoie ces HLS que pour certaines sessions
jugées "trusted". Le log dira donc immédiatement s'ils sont disponibles sur
cette machine/session.
## Test
Extraire le ZIP à la racine :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Tester 1080p.
Succès attendu :
```text
[HD-WINDOWS][17.13] tentative HLS muxé web_safari cible=1080p
[HD-WINDOWS][17.13] HLS muxé trouvé: ... audio=...
[HD-WINDOWS][17.13] HLS AV confirmé: 1920x1080 (vidéo + audio muxés)
```
Si le HLS n'est pas disponible :
```text
[HD-WINDOWS][17.13] aucun HLS muxé web_safari ...
[HD-WINDOWS][17.13] HLS muxé indisponible/illisible -> secours mweb adaptatif
```
+100
View File
@@ -0,0 +1,100 @@
# Étape 17.14 - Priorité web_embedded Windows
Le test manuel avec le yt-dlp nightly 2026.08.18.122307 est décisif.
Pour la vidéo de validation, `web_embedded` expose :
```text
251 webm audio only opus
299 mp4 1920x1080 60 AVC
308 webm 2560x1440 60 VP9
315 webm 3840x2160 60 VP9
```
Et surtout le test utilisateur a téléchargé intégralement le format audio 251 :
```text
[download] 100% of 10.88MiB
```
## Architecture 17.14
Le chemin prioritaire Windows devient :
```text
yt-dlp nightly
↓
client web_embedded
↓
URL vidéo adaptative + URL audio adaptative
↓
headers exacts yt-dlp
↓
media_kit / libmpv
```
Aucun PO Token n'est utilisé sur ce chemin.
Le format audio 251 Opus est préféré explicitement car c'est celui qui a été
validé intégralement en ligne de commande.
Vidéo :
- <=1080p : AVC/H.264 prioritaire, donc typiquement format 299 en 1080p60 ;
- 1440p/2160p : VP9 prioritaire, donc typiquement 308/315.
## Fallback
Si la vidéo n'est pas embeddable ou si libmpv refuse les URLs directes :
1. tentative HLS web_safari pour <=1080p ;
2. ancien pipeline mweb + PO Token/relais ;
3. fallback stable 360p existant.
Android n'est pas modifié.
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Tester 1080p d'abord.
Logs attendus :
```text
[HD-WINDOWS][17.14] priorité web_embedded directe ...
[HD-WINDOWS][17.14] paire embedded: 1920x1080 60fps video=299/... audio=251/opus
[HD-WINDOWS][17.14] vidéo embedded confirmée: 1920x1080
[HD-WINDOWS][17.14] audio embedded attaché: format=251 codec=opus
[HD-WINDOWS][17.14] lecture AV embedded confirmée: 1920x1080
```
Ensuite seulement, tester 2160p :
```text
video=315/vp9
...
lecture AV embedded confirmée: 3840x2160
```
## PREPARER_HD_WINDOWS.ps1 mis à jour
Le script de préparation est inclus dans cette étape afin d'éviter de
réinstaller accidentellement l'ancien yt-dlp stable 2026.07.04.
Il télécharge toujours ce stable comme bootstrap vérifié par SHA-256 puis
exécute automatiquement :
```text
yt-dlp.exe --update-to nightly
```
Ainsi les futures préparations Windows conservent le canal nightly requis par
ce chemin `web_embedded`.
+91
View File
@@ -0,0 +1,91 @@
# Étape 17.15 - Cache progressif yt-dlp web_embedded
## Diagnostic confirmé
Le test manuel complet a réussi :
```text
format 299
361.06 MiB
100 %
24 secondes
```
Le format audio 251 avait déjà réussi intégralement :
```text
10.88 MiB
100 %
```
Cela prouve que le téléchargement `web_embedded` fonctionne réellement pour
les deux pistes avec yt-dlp nightly.
En revanche, le log 17.14 montre que libmpv échoue dès qu'on lui donne
directement l'URL googlevideo.
## Architecture 17.15
On ne donne donc plus aucune URL googlevideo `web_embedded` à libmpv.
```text
yt-dlp nightly web_embedded
↓
fichier vidéo temporaire qui grandit
fichier audio temporaire qui grandit
↓
serveur 127.0.0.1 intégré
↓
media_kit / libmpv
```
Les deux téléchargements yt-dlp démarrent en parallèle.
Le lecteur n'attend pas forcément le téléchargement complet :
le serveur local lit les fichiers pendant qu'ils grandissent.
Si mpv demande une zone située plus loin lors d'un seek, le serveur attend
simplement que yt-dlp ait téléchargé cette zone.
## Formats
Pour la vidéo de validation :
```text
1080p60 : vidéo 299 AVC + audio 251 Opus
1440p60 : vidéo 308 VP9 + audio 251 Opus
2160p60 : vidéo 315 VP9 + audio 251 Opus
```
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Tester 1080p + son.
Logs attendus :
```text
[HD-WINDOWS][17.15] priorité web_embedded via cache yt-dlp progressif
[HD-CACHE][17.15] lancement yt-dlp video format=299
[HD-CACHE][17.15] lancement yt-dlp audio format=251
[HD-CACHE][17.15] actif sur 127.0.0.1:...
[HD-WINDOWS][17.15] vidéo cache confirmée: 1920x1080
[HD-WINDOWS][17.15] audio cache attaché: format=251 codec=opus
[HD-WINDOWS][17.15] lecture AV cache confirmée: 1920x1080
```
Le téléchargement peut démarrer avec quelques secondes d'attente imposées par
YouTube/yt-dlp, comme dans le test manuel où yt-dlp a affiché :
```text
Sleeping 6.00 seconds as required by the site...
```
Une fois le 1080p + son validé, tester le 2160p.
+72
View File
@@ -0,0 +1,72 @@
# Étape 17.16 - Mode robuste Windows : téléchargement complet puis lecture locale
Le cache progressif 17.15 a échoué parce que libmpv ouvre le serveur local
avant que le fichier vidéo soit suffisamment disponible. Le log montre :
```text
[HD-CACHE][17.15] /video GET ...
[MPV-ERROR] Failed to open http://127.0.0.1:.../video
```
En revanche, l'audio 251 a bien été téléchargé complètement dans le même test.
Nous arrêtons donc les expériences de streaming/relais pour stabiliser enfin
le chemin Windows.
## Ce qui est déjà prouvé
Les tests manuels utilisateur ont validé avec `web_embedded` :
```text
audio 251 : 10.88 MiB -> 100 %
vidéo 299 : 361.06 MiB -> 100 % en ~24 s
```
## Architecture 17.16
```text
yt-dlp web_embedded
├── vidéo -> téléchargement complet local
└── audio -> téléchargement complet local
(les deux en parallèle)
↓
fichiers temporaires complets
↓
media_kit / libmpv
```
Aucune URL googlevideo n'est utilisée par libmpv.
Aucun serveur HTTP local n'est utilisé pour ce chemin.
Le démarrage 1080p prendra donc typiquement autour de 20-30 secondes avec la
vidéo de test actuelle. Ce n'est pas encore l'expérience finale, mais c'est le
chemin de référence destiné à obtenir une lecture HD + son totalement stable.
Une fois ce socle validé, l'optimisation du démarrage pourra être faite sans
remettre en cause le transport réseau.
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Tester 1080p et attendre la fin du téléchargement.
Logs attendus :
```text
[HD-WINDOWS][17.16] mode robuste: téléchargement complet web_embedded
[HD-DOWNLOAD][17.16] téléchargement parallèle video=299 audio=251
[HD-DOWNLOAD][17.16] audio terminé: ...
[HD-DOWNLOAD][17.16] video terminé: ...
[HD-DOWNLOAD][17.16] prêts: ...
[HD-WINDOWS][17.16] fichiers locaux prêts -> ouverture media_kit
[HD-WINDOWS][17.16] vidéo locale confirmée: 1920x1080
[HD-WINDOWS][17.16] audio local attaché: format=251 codec=opus
[HD-WINDOWS][17.16] lecture AV locale confirmée: 1920x1080
```
+23
View File
@@ -0,0 +1,23 @@
# Étape 17.17.1 - Correctif compilation Windows
Le build 17.17 échouait sur :
```text
The method 'setLastModified' isn't defined for the type 'Directory'
```
Correction :
- suppression de l'appel invalide sur `Directory` ;
- ajout d'un fichier marqueur `.last_access` dans chaque dossier de cache ;
- le nettoyage LRU lit désormais la date de ce marqueur ;
- aucun changement du lecteur, de yt-dlp ou du choix AV1.
Test :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
`flutter clean` n'est pas nécessaire.
+94
View File
@@ -0,0 +1,94 @@
# Étape 17.17 - Démarrage Windows accéléré sans changer le transport validé
La 17.16 est notre première base Windows réellement validée :
```text
vidéo locale confirmée: 1920x1080
audio local attaché: format=251 codec=opus
lecture AV locale confirmée: 1920x1080
```
On ne remplace donc PAS cette architecture.
## Optimisation 1 : format vidéo beaucoup plus compact
La vidéo de test expose en 1080p60 :
```text
299 AVC : ~361 MiB
303 VP9 : ~275 MiB
399 AV1 : ~172 MiB
```
La 17.16 choisissait 299.
La 17.17 choisit maintenant le codec efficace pour la résolution demandée,
donc typiquement 399 AV1 en 1080p60.
À débit réseau identique, cela divise approximativement par deux le volume
vidéo du premier chargement.
Même principe :
```text
1440p60 : AV1 400 préféré
2160p60 : AV1 401 préféré
```
## Optimisation 2 : cache persistant
Les fichiers complets validés ne sont plus supprimés à la fermeture du player.
Emplacement :
```text
%LOCALAPPDATA%\YouTubePersonal\media_cache
```
Au prochain lancement de la même vidéo/qualité :
```text
[HD-CACHE][17.17] video=HIT audio=HIT
```
et media_kit peut ouvrir immédiatement les fichiers locaux sans les
retélécharger.
Le cache conserve automatiquement les 5 vidéos les plus récentes.
## Optimisation 3 : progression visible
Pendant le premier chargement, l'écran affiche maintenant l'avancement :
```text
Préparation HD • vidéo 45% • audio 100%
```
L'application n'a donc plus l'air figée.
## Test
Extraire le ZIP à la racine.
Un `flutter clean` n'est pas nécessaire à chaque test. Faire simplement :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Tester 1080p.
Le premier lancement doit sélectionner typiquement :
```text
[HD-WINDOWS][17.17] format rapide sélectionné: 399 1920x1080 60fps av01...
```
Puis, au deuxième lancement de la même vidéo :
```text
[HD-CACHE][17.17] video=HIT audio=HIT
```
Le chargement réseau doit alors disparaître presque totalement.
+104
View File
@@ -0,0 +1,104 @@
# Étape 17.18 - Lecture immédiate puis bascule HD en arrière-plan
La 17.17 a montré deux choses :
1. Le format AV1 399 réduit bien la taille (environ 180 Mo au lieu de 379 Mo),
mais l'audio externe n'était plus audible sur la machine de test.
2. Même avec un fichier deux fois plus petit, attendre le téléchargement
complet AVANT de démarrer donne encore l'impression d'un lecteur lent.
On change donc l'expérience au lieu de continuer à réduire quelques secondes.
## Nouveau comportement
```text
clic vidéo
↓
360p muxé stable démarre immédiatement (image + son)
↓
en parallèle :
yt-dlp télécharge la HD + l'audio dans le cache
↓
quand la HD est prête :
position actuelle capturée
↓
bascule automatique vers la HD locale
↓
seek à la même position
```
L'utilisateur ne regarde donc plus un écran de chargement pendant les 20-30 s
du premier téléchargement.
## Audio
Pour 1080p Windows, on revient volontairement à la paire déjà VALIDÉE :
```text
vidéo 299 AVC 1080p60
audio 251 Opus
```
C'est exactement la paire qui avait donné du son en 17.16.
L'AV1 399 n'est plus prioritaire en 1080p Windows pour le moment.
## Cache
Le cache persistant 17.17 reste actif.
Première lecture :
```text
360p immédiat
↓
téléchargement HD en arrière-plan
↓
bascule 1080p
```
Deuxième lecture de la même vidéo :
```text
360p immédiat
↓
cache HIT
↓
bascule 1080p presque immédiate
```
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Logs attendus au premier lancement :
```text
[HD-WINDOWS][17.18] lecture immédiate 360p pendant préparation cible=1080
[HD-WINDOWS][17.18] préparation HD arrière-plan cible=1080
[HD-WINDOWS][17.18] HD arrière-plan: 1920x1080 60fps video=299/... audio=251/opus
```
La vidéo 360p doit déjà jouer avec le son pendant ces lignes.
Quand le téléchargement est terminé :
```text
[HD-WINDOWS][17.18] bascule 360p -> HD à ... ms
[HD-WINDOWS][17.18] bascule HD confirmée: 1920x1080 audio=251/opus
```
Au deuxième lancement :
```text
[HD-CACHE][17.17] video=HIT audio=HIT
```
et la bascule doit être très rapide.
+107
View File
@@ -0,0 +1,107 @@
# Étape 17.19 - Vrai prébuffer HD progressif Windows
Oui : c'est désormais exactement l'architecture utilisée.
La 17.18 attendait encore les 378 Mo complets du format 299 avant de basculer.
La 17.19 ne fait plus cela.
## Fonctionnement
```text
clic
↓
360p muxé démarre immédiatement
↓
yt-dlp démarre 1080p 299 + audio 251
↓
on surveille la taille réellement téléchargée
↓
dès qu'on a environ le timestamp courant + 20 s de réserve :
BASCULE HD
↓
yt-dlp continue à télécharger le reste pendant la lecture
```
Exemple avec une reprise autour de 60 secondes :
```text
on ne demande plus 378 Mo
on attend seulement assez d'octets pour couvrir environ 0 -> 80 s
+ une marge de sécurité
```
Avec le débit observé lors du test manuel, cela devrait être beaucoup plus
court que le téléchargement intégral.
## Pourquoi pas `--download-sections`
`yt-dlp --download-sections` passe par ffmpeg pour les plages temporelles.
Pour notre lecteur, on veut au contraire conserver le fichier DASH continu
qui grandit pendant la lecture et laisser mpv faire ses Range/seeks.
## Correction du flash au début
La 17.18 faisait :
```text
open HD au début
puis seek vers 94.8 s
```
La 17.19 utilise directement :
```dart
Media(
hdUrl,
start: switchPosition,
)
```
Donc mpv prépare le nouveau média au timestamp courant avant la première frame
HD rendue.
## Audio
On conserve la paire validée :
```text
1080p60 : 299 AVC
audio : 251 Opus
```
L'audio étant petit, la bascule attend qu'il soit entièrement disponible afin
de ne pas perdre le son.
## Cache
Le cache existant est réutilisé :
```text
%LOCALAPPDATA%\YouTubePersonal\media_cache
```
Si 299 + 251 sont déjà complets, la bascule peut être quasi immédiate.
## Test conseillé
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Pour voir le comportement progressif réel, tester aussi UNE AUTRE vidéo qui
n'est pas déjà en cache.
Logs attendus :
```text
[HD-WINDOWS][17.19] lecture immédiate 360p ...
[HD-PROGRESSIVE][17.19] session prête video=STREAM audio=DOWNLOAD
[HD-WINDOWS][17.19] buffer suffisant: video=... ≈...s audio=true
[HD-WINDOWS][17.19] bascule vers HD bufferisée à ... ms
[HD-WINDOWS][17.19] HD progressive confirmée: 1920x1080 audio=251/opus
```
+24
View File
@@ -0,0 +1,24 @@
# Étape 17.1 - Correctif checksum Deno
Le téléchargement de Deno était correct, mais le script lisait mal le fichier
`.sha256sum` et interprétait le mot `path` comme s'il s'agissait du hash.
Le correctif extrait maintenant explicitement l'unique SHA-256 de 64 caractères
hexadécimaux avant de comparer le fichier téléchargé.
Après extraction de ce ZIP à la racine du projet :
```powershell
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\PREPARER_HD_WINDOWS.ps1
```
Si la préparation termine correctement :
```powershell
flutter clean
flutter pub get
flutter run -d windows
```
Ensuite tester 1080p puis 2160p (4K).
+57
View File
@@ -0,0 +1,57 @@
# Étape 17.20 - Raccourcis clavier en plein écran
Ajout demandé pour le mode plein écran Windows.
## Raccourcis
```text
Espace = Lecture / Pause
Flèche gauche = Reculer de 10 secondes
Flèche droite = Avancer de 10 secondes
Flèche haut = Volume +5 %
Flèche bas = Volume -5 %
F = Entrer / quitter le plein écran
Échap = Quitter le plein écran
```
Les flèches gauche/droite utilisent 10 secondes pour rester cohérentes avec
les boutons -10/+10 déjà présents dans l'application.
## Important
Les raccourcis personnalisés sont appliqués uniquement au thème plein écran
desktop. Le comportement normal hors plein écran n'est donc pas perturbé.
`media_kit_video` permet officiellement de personnaliser les raccourcis avec
`MaterialDesktopVideoControlsThemeData.keyboardShortcuts`.
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Passer la vidéo en plein écran puis tester :
```text
Espace
←
→
↑
↓
F
Échap
```
Des logs apparaissent aussi pour les actions seek/volume :
```text
[FULLSCREEN-KEYS] seek -10s
[FULLSCREEN-KEYS] seek +10s
[FULLSCREEN-KEYS] volume=55%
```
+86
View File
@@ -0,0 +1,86 @@
# Étape 17.21 - Qualités 1440p / 4K strictes sous Windows
Le log explique exactement pourquoi le bouton 4K restait en 1080p.
Lors du clic 4K :
```text
[HD] changement qualité -> 2160p (4K)
[HD-WINDOWS][17.14] extraction web_embedded cible=2160
[HD-WINDOWS][17.14] paire embedded: 1920x1080 ... video=303/vp9
```
Puis la lecture était confirmée en :
```text
1920x1080
```
Même chose pour 1440p : l'extracteur renvoyait encore 1920x1080.
Cela signifie que, pour cette vidéo/session web_embedded, la liste yt-dlp
utilisée par l'application ne contient pas 1440p/2160p. L'ancien code
acceptait silencieusement la meilleure qualité inférieure, donc 1080p.
## Correction 17.21
Sur Windows, pour 1440p et 2160p :
1. l'application vérifie d'abord les formats web_embedded sans interrompre
la vidéo en cours ;
2. si la résolution demandée existe, le passage de qualité continue ;
3. si elle n'existe pas, le Player actuel reste intact ;
4. une notification explique la résolution maximale disponible ;
5. la qualité absente est ensuite désactivée dans le menu pour cette vidéo.
Exemple :
```text
2160p (4K) n'est pas disponible pour cette vidéo.
Cette vidéo est disponible ici jusqu'à 1080p.
```
Et dans le menu :
```text
1440p • indisponible
2160p (4K) • indisponible
```
## Important
Ce correctif ne bloque PAS la vraie 4K.
Si web_embedded expose réellement 2160p pour une vidéo, `_selectVideo`
sélectionne toujours la résolution la plus haute <= cible. Le chemin
progressif 17.19 continuera alors avec le flux 2160p réel.
Le service journalise maintenant aussi la liste réelle :
```text
[HD-WINDOWS][17.21] cible 2160p absente pour web_embedded;
disponibles=[360, 480, 720, 1080]
```
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Tester d'abord la même vidéo. Le clic 4K ne doit plus faire :
1080p -> 360p -> 1080p.
Ensuite tester une vidéo dont la source est réellement disponible en 4K.
Si yt-dlp expose 2160p, les logs doivent montrer :
```text
paire embedded: 3840x2160 ...
flux progressif: 3840x2160 ...
HD progressive confirmée: 3840x2160 ...
```
+74
View File
@@ -0,0 +1,74 @@
# Étape 17.2 - Correctif HD/4K Windows via mpv ytdl_hook
## Diagnostic confirmé par le log précédent
yt-dlp fonctionnait parfaitement :
- 1080p trouvé : `1920x1080 60fps avc1`
- 4K trouvée : `3840x2160 60fps vp9`
Mais après avoir transmis les URLs `googlevideo` directement à media_kit,
aucune image HD n'arrivait dans les 12 secondes. Le lecteur revenait donc en
640x360.
## Nouvelle stratégie
Windows n'ouvre plus manuellement les URLs directes yt-dlp.
On laisse maintenant **le ytdl_hook natif de mpv** :
1. appeler le `yt-dlp.exe` déjà empaqueté ;
2. utiliser Deno ;
3. choisir la qualité demandée ;
4. appliquer lui-même les paramètres/headers nécessaires ;
5. associer lui-même la piste vidéo et la piste audio ;
6. fournir le résultat directement à media_kit/libmpv.
Android n'est pas modifié et conserve le chemin NewPipe direct déjà validé.
## Installation
Les outils Windows sont déjà installés. Il n'est pas nécessaire de relancer
`PREPARER_HD_WINDOWS.ps1`.
Extraire ce ZIP à la racine du projet puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter pub get
flutter run -d windows
```
Tester d'abord 1080p puis 2160p.
## Logs attendus
Au démarrage du flux :
```text
[HD-WINDOWS] préparation mpv/ytdl_hook ...
[HD-WINDOWS] configuration ytdl_hook ...
[HD-WINDOWS] ouverture via ytdl_hook ...
```
Succès 1080p :
```text
[HD-WINDOWS] lecture confirmée: 1920x1080 ...
```
Succès 4K :
```text
[HD-WINDOWS] lecture confirmée: 3840x2160 ...
```
Si mpv/yt-dlp renvoie une erreur, elle est maintenant remontée avec :
```text
[HD-WINDOWS][MPV-ERROR] ...
```
Le délai de validation Windows passe aussi de 12 à 30 secondes, car mpv lance
désormais yt-dlp + Deno lui-même avant d'ouvrir les pistes.
+66
View File
@@ -0,0 +1,66 @@
# Étape 17.3 - Forcer réellement le ytdl_hook Windows
Le log précédent a montré ceci :
```text
[HD-WINDOWS] ouverture via ytdl_hook ...
[HD-WINDOWS][MPV] cplayer: Failed to recognize file format.
```
Le hook n'avait donc pas pris possession de l'URL avant que mpv tente
d'interpréter la page HTML YouTube comme un fichier vidéo.
## Correctif
L'URL ouverte par mpv devient maintenant :
```text
ytdl://https://www.youtube.com/watch?v=...
```
Le préfixe `ytdl://` force explicitement le hook intégré de mpv à traiter
l'URL avec yt-dlp.
Le chemin absolu de notre `yt-dlp.exe` reste fourni via :
```text
ytdl_hook-ytdl_path=...
```
Deno reste fourni à yt-dlp avec `ytdl-raw-options`.
## Diagnostic ajouté
Après l'ouverture, l'application journalise également :
```text
[HD-WINDOWS] ytdl_hook path=...
```
Si le hook est bien exécuté, ce chemin doit pointer vers le `yt-dlp.exe`
empaqueté dans `flutter_assets`.
## Installation
Aucun script de préparation à relancer.
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter pub get
flutter run -d windows
```
Tester d'abord 1080p.
Succès attendu :
```text
[HD-WINDOWS] ouverture FORCÉE via ytdl:// ...
[HD-WINDOWS] ytdl_hook path=...yt-dlp.exe
[HD-WINDOWS] lecture confirmée: 1920x1080 ...
```
Puis tester 2160p.
+72
View File
@@ -0,0 +1,72 @@
# Étape 17.5 - Correctif exact media_kit : loadfile au lieu de loadlist
## Ce que le log 17.4 montre
mpv bloque encore :
```text
Refusing to load potentially unsafe URL from a playlist.
```
Le problème n'est plus yt-dlp.
## Cause confirmée dans media_kit
`Player.open(Media(...))` écrit le média dans une playlist temporaire puis
utilise la commande mpv `loadlist`.
Or `ytdl://` est un protocole spécial. mpv refuse les protocoles spéciaux
lorsqu'ils proviennent d'une playlist.
Dans le code de media_kit, `Player.add(Media(...))` passe au contraire par
`loadfile` directement.
## Correctif 17.5
Pour Windows HD/4K uniquement :
1. `player.stop()`
2. `player.add(Media("ytdl://..."))`
3. `player.play()`
Cela utilise `loadfile` directement et ne nécessite plus de désactiver
globalement la sécurité des playlists.
Le réglage `load-unsafe-playlists=yes` ajouté en 17.4 est donc supprimé.
## Installation
Extraire ce ZIP à la racine du projet puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Pas besoin de relancer `PREPARER_HD_WINDOWS.ps1`.
Pas besoin de `flutter pub get`.
## Log attendu
Au minimum :
```text
[HD-WINDOWS][17.5] mode loadfile direct actif
[HD-WINDOWS] ouverture FORCÉE via ytdl:// ...
[HD-WINDOWS] ytdl:// chargé via loadfile direct (pas via loadlist)
```
L'erreur suivante doit avoir disparu :
```text
Refusing to load potentially unsafe URL from a playlist.
```
Puis, si yt-dlp/mpv ouvre bien le flux :
```text
[HD-WINDOWS] lecture confirmée: 1920x1080 ...
```
Ensuite tester 2160p.
+44
View File
@@ -0,0 +1,44 @@
# Étape 17.6 - Windows direct avec headers yt-dlp
## Ce que le test 17.5 a confirmé
Le chemin `ytdl://` ne déclenche toujours aucun hook yt-dlp dans le libmpv
embarqué par media_kit. Plutôt que de continuer à dépendre de ce hook, cette
étape revient aux URLs directes yt-dlp, mais corrige les deux points qui
manquaient au premier essai Windows.
## Changements
1. Les headers HTTP renvoyés par yt-dlp sont récupérés.
2. Le User-Agent / Referer / Cookie / X-Forwarded-For sont appliqués à libmpv.
3. La vidéo HD est ouverte et validée AVANT d'ajouter la piste audio externe.
4. La piste audio est ajoutée seulement après confirmation de la résolution.
5. Les erreurs HTTP/FFmpeg/libavformat sont journalisées.
Android n'est pas modifié.
## Installation
Extraire le ZIP à la racine du projet :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Pas besoin de relancer `PREPARER_HD_WINDOWS.ps1`.
Pas besoin de `flutter pub get`.
## Logs attendus en 1080p
```text
[HD-WINDOWS][17.6] extraction directe + headers ...
[HD-WINDOWS] flux sélectionné: 1920x1080 ...
[HD-WINDOWS] headers appliqués à libmpv: ...
[HD-WINDOWS] vidéo seule confirmée: 1920x1080
[HD-WINDOWS] lecture confirmée: 1920x1080
```
Si Googlevideo refuse la requête, les logs doivent maintenant afficher par
exemple `403`, `Forbidden` ou `HTTP error`, ce qui donnera la cause exacte.
+75
View File
@@ -0,0 +1,75 @@
# Étape 17.7 - Correction exacte des headers mpv Windows
L'autre analyse a mis le doigt sur un point important, et la vérification du
code/documentation mpv/media_kit confirme deux choses :
1. `Media(..., httpHeaders: ...)` couvre bien le média principal.
2. `AudioTrack.uri(...)` ne prend pas de `httpHeaders`.
Il faut donc aussi appliquer les headers globalement à l'instance libmpv avant
`open()`, pour que la piste audio externe les hérite.
## Bug dans notre étape 17.6
Notre code utilisait :
```text
options/user-agent
options/http-header-fields
```
pour `NativePlayer.setProperty`.
C'était le mauvais niveau de propriété pour cette API runtime.
La 17.7 utilise maintenant directement :
```text
user-agent
http-header-fields
```
## Autre correction
La 17.6 n'envoyait globalement que quelques headers.
La 17.7 transmet maintenant TOUS les headers renvoyés par yt-dlp, sauf
`User-Agent` qui utilise la propriété mpv dédiée.
Les virgules à l'intérieur des valeurs sont échappées avant de construire la
string-list `http-header-fields`.
## Lecture
L'ordre reste :
1. yt-dlp extrait vidéo + audio + headers
2. headers globaux mpv
3. `Media(videoUrl, httpHeaders: headers)`
4. démarrage et validation de la vidéo HD
5. réapplication des headers globaux
6. `AudioTrack.uri(audioUrl)`
7. confirmation finale de la résolution
## Test
Extraire à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Pas besoin de relancer `PREPARER_HD_WINDOWS.ps1`.
Pas besoin de `flutter pub get`.
Logs attendus :
```text
[HD-WINDOWS][17.7] extraction directe + headers globaux mpv ...
[HD-WINDOWS][17.7] headers globaux mpv appliqués: UA=true, champs=...
[HD-WINDOWS][17.7] propriétés mpv posées AVANT open()
[HD-WINDOWS] vidéo seule confirmée: 1920x1080
[HD-WINDOWS] lecture confirmée: 1920x1080
```
+75
View File
@@ -0,0 +1,75 @@
# Étape 17.8 - Forcer le client YouTube TV dans yt-dlp
Le log 17.7 a donné une information décisive : l'URL HD sélectionnée contenait :
```text
c=ANDROID_VR
```
Les headers sont désormais bien appliqués à libmpv, mais l'URL issue de ce
client échoue encore à l'ouverture.
## Changement 17.8
yt-dlp est maintenant lancé avec :
```text
--extractor-args
youtube:player_client=tv
```
L'objectif est d'éviter complètement le client `ANDROID_VR`.
Tous les correctifs 17.7 sont conservés :
- extraction locale yt-dlp ;
- Deno ;
- URLs vidéo/audio séparées ;
- `Media.httpHeaders` pour la vidéo ;
- `user-agent` global mpv ;
- `http-header-fields` global mpv ;
- réapplication des headers avant `AudioTrack.uri`.
## Diagnostic supplémentaire
Le code lit maintenant le paramètre `c=` de l'URL sélectionnée sans afficher
l'URL signée complète.
Log attendu :
```text
[HD-WINDOWS][17.8] client yt-dlp forcé: tv
[HD-WINDOWS] flux sélectionné: 1920x1080 ... client=TV
```
La valeur exacte peut être `TV` ou une variante utilisée par YouTube, mais elle
ne doit plus être `ANDROID_VR`.
## Installation
Extraire ce ZIP à la racine du projet puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter run -d windows
```
Pas besoin de relancer `PREPARER_HD_WINDOWS.ps1`.
Pas besoin de `flutter pub get`.
Tester d'abord 1080p.
Succès attendu :
```text
[HD-WINDOWS][17.8] client yt-dlp forcé: tv
[HD-WINDOWS] flux sélectionné: 1920x1080 ... client=TV
[HD-WINDOWS] vidéo seule confirmée: 1920x1080
[HD-WINDOWS] lecture confirmée: 1920x1080
```
Si le client indiqué reste `ANDROID_VR`, le paramètre `extractor-args` n'a pas
été pris en compte. Si le client est `TV` mais l'ouverture échoue encore, le
prochain log permettra de séparer clairement un blocage serveur d'un problème
libmpv.
+45
View File
@@ -0,0 +1,45 @@
# Étape 17.9 - mweb + PO Token provider local
Le test 17.8 montre que le client `tv` n'est pas exploitable pour cette vidéo :
```text
ERROR: [youtube] ... The page needs to be reloaded.
```
Cette étape suit donc la voie recommandée actuellement par yt-dlp :
client `mweb` + fournisseur automatique de PO Token.
Le provider est local à la machine et est invoqué à la demande via Deno.
Aucun cookie Google de l'application n'est transmis à yt-dlp.
## Installation une seule fois
Extraire le ZIP à la racine puis :
```powershell
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\PREPARER_PO_TOKEN_WINDOWS.ps1
```
Puis :
```powershell
flutter clean
flutter run -d windows
```
## Logs attendus
```text
[HD-WINDOWS][17.9] extraction mweb + PO Token provider local
[HD-WINDOWS] flux sélectionné: 1920x1080 ... client=MWEB
```
Puis :
```text
[HD-WINDOWS] vidéo seule confirmée: 1920x1080
[HD-WINDOWS] lecture confirmée: 1920x1080
```
Le correctif global des headers mpv de la 17.7 reste en place.
+66
View File
@@ -0,0 +1,66 @@
# Étape 17 - HD / 4K Windows
Cette étape ajoute au lecteur Windows le même sélecteur :
- Auto
- 360p
- 720p
- 1080p
- 1440p
- 2160p (4K)
## Architecture Windows
Android continue d'utiliser NewPipeExtractor.
Windows utilise localement :
- yt-dlp.exe
- Deno
- media_kit / libmpv pour lire la vidéo et l'audio séparés.
Aucun serveur PC, backend distant personnel ou compte Google n'est nécessaire.
Le chemin OAuth officiel YouTube Data API reste séparé : aucun cookie Google ou
jeton OAuth n'est transmis à yt-dlp.
## Installation des outils Windows
Après extraction du ZIP à la racine :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
.\PREPARER_HD_WINDOWS.ps1
```
Le script télécharge les releases officielles et vérifie leur SHA-256.
Puis :
```powershell
flutter clean
flutter pub get
flutter run -d windows
```
## Logs attendus
1080p :
```text
[HD-WINDOWS] flux sélectionné: 1920x1080 ...
[HD] lecture confirmée: 1920x1080 ...
```
4K :
```text
[HD-WINDOWS] flux sélectionné: 3840x2160 ...
[HD] lecture confirmée: 3840x2160 ...
```
Si l'extraction ou le décodage 1440p/4K échoue, l'application tente 1080p puis
revient au flux 360p stable.
## Important
La première préparation Windows télécharge deux exécutables. Une fois ceux-ci
placés dans `assets/windows_hd/`, Flutter les empaquette dans l'application.
+26
View File
@@ -0,0 +1,26 @@
# Étape 18.1 - Correctif compilation Radar
Erreur corrigée :
```text
lib/services/radar_service.dart(...): Constant evaluation error
```
Cause :
le `const Set<String> _stopWords` contenait des valeurs en double (`the` et `que`).
Dart refuse les doublons dans un `const Set` à l'évaluation de compilation.
Correction :
- suppression des doublons uniquement ;
- aucun changement du fonctionnement du Radar ;
- aucun changement de la partie HD.
Test :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
+105
View File
@@ -0,0 +1,105 @@
# Étape 18.2 - Parité Android
Cette étape met Android au même niveau fonctionnel pour les nouveautés
récentes, sans remplacer son moteur HD natif qui fonctionne déjà mieux que
le chemin Windows.
## Ce qui est commun Windows + Android
Le Radar V1 est du Flutter partagé, donc il fonctionne maintenant sur les
deux plateformes :
```text
Pour toi maintenant
Continuer ce sujet
Quelque chose de différent
À redécouvrir
Pourquoi cette vidéo ?
Pas maintenant
Moins de cette chaîne
Mélanger mon Radar
```
Modes de session :
```text
Comme d'habitude
Je veux du nouveau
Apprendre
Détente
Courtes vidéos
Long format
```
Historique, favoris et feedback du Radar utilisent les mêmes préférences
locales de l'application.
## Android : commandes plein écran tactiles
Les raccourcis clavier Windows n'ont pas de sens sur téléphone. Leur
équivalent tactile est ajouté :
```text
Tap simple = lecture / pause
Double tap moitié gauche = -10 secondes
Double tap moitié droite = +10 secondes
Glisser vertical à droite = volume
```
Les boutons physiques de volume Android continuent naturellement à
fonctionner.
## HD Android
AUCUN des relais/cache yt-dlp Windows n'est porté sur Android.
Android garde le chemin natif déjà validé :
```text
360p
720p
1080p
1440p
2160p / 4K
```
avec NewPipeExtractor / MediaCodec.
C'est volontaire : importer le mécanisme Windows sur Android serait une
régression.
## Conservé
```text
PiP Android
lecture écran verrouillé / arrière-plan
reprise de lecture
vitesse
favoris
historique
abonnements Google
qualités jusqu'à 4K
```
## Test Android
Après extraction à la racine :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d f10778d8
```
Il n'est pas nécessaire de faire `flutter clean`.
Tester :
1. Accueil -> le Radar doit apparaître.
2. Changer de mode de session.
3. Ouvrir "Pourquoi cette vidéo ?".
4. Ouvrir une vidéo.
5. Passer en plein écran.
6. Double tap gauche puis droite.
7. Glisser verticalement sur la moitié droite pour le volume.
8. Vérifier 1080p puis 2160p/4K.
9. Vérifier PiP et lecture écran verrouillé.
+155
View File
@@ -0,0 +1,155 @@
# Étape 18.3 - Chaînes + catégories Radar
Trois ajouts principaux, communs à Windows et Android.
## 1. Cliquer sur la chaîne depuis une vidéo
Le nom de l'auteur sous le lecteur est maintenant cliquable.
```text
Vidéo
↓
nom de la chaîne
↓
page de la chaîne
↓
toutes ses vidéos publiques
```
Cela ne dépend pas du compte Google. Même une vidéo venant d'une recherche,
du Radar, de l'historique ou des favoris peut résoudre sa chaîne via
youtube_explode_dart.
Les anciennes vidéos déjà enregistrées dans l'historique restent compatibles :
si elles n'ont pas encore `channelId`, l'application le résout à partir du
videoId au clic.
## 2. Page chaîne = catalogue complet progressif
La page d'une chaîne charge les uploads progressivement au lieu d'attendre
tout le catalogue.
Tri disponible :
```text
Plus récentes
Plus vues
Plus anciennes
```
Chaque vidéo récupère maintenant aussi :
```text
channelId
viewCount
uploadDate
```
## 3. Nouveau menu "Chaînes"
Navigation :
```text
Accueil
Recherche
Abonnements
Chaînes
Historique
Favoris
```
Le menu Chaînes construit un catalogue à partir :
```text
historique
favoris
Radar actuel
```
Puis enrichit les anciennes vidéos qui n'avaient pas encore les métadonnées
de chaîne/vues.
Classement principal :
```text
somme des vues publiques des vidéos connues pour chaque chaîne
```
Exemple :
```text
#1 Chaîne A 85 M vues cumulées
#2 Chaîne B 42 M vues cumulées
#3 Chaîne C 18 M vues cumulées
```
Le texte "vidéos mesurées" est volontaire : ce n'est pas la somme de TOUTES
les vues historiques de toute la chaîne, seulement des vidéos que YouTube
Personal connaît déjà via tes sources personnelles.
## 4. Radar trié par catégories
Nouvelle zone :
```text
Explorer par catégorie
```
Catégories V1 :
```text
Jeux vidéo
Culture & histoire
OVNI & mystères
Technologie & IA
Science & espace
Auto & transport
Musique
Société & actualité
Tutoriels & apprentissage
Divers
```
Le classement est local et transparent, à partir du titre + nom de chaîne.
Une vidéo est placée dans une catégorie principale pour éviter les doublons.
La liste des catégories n'est pas figée visuellement : seules les catégories
qui ont réellement des propositions apparaissent.
## Important
Le système HD Windows/Android n'est pas modifié dans cette étape.
Les gestes plein écran Android et raccourcis Windows de l'étape 18.2 restent
dans `video_player_page.dart`.
## Test
Extraire le ZIP à la racine puis :
Windows :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Android :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d f10778d8
```
Pas besoin de `flutter clean`.
Scénario de test :
1. Ouvrir une vidéo.
2. Cliquer le nom de la chaîne.
3. Vérifier que ses vidéos commencent à apparaître.
4. Tester le tri "Plus vues".
5. Revenir et ouvrir le menu "Chaînes".
6. Vérifier le classement par vues.
7. Accueil -> "Explorer par catégorie".
8. Tester "Jeux vidéo", "Culture & histoire", "OVNI & mystères".
@@ -0,0 +1,156 @@
# Étape 18.4 - Chaînes personnelles + chargement vidéos + SponsorBlock
Cette étape corrige les trois points remontés après la capture du menu
Chaînes.
## 1. Les vidéos d'une chaîne se chargent réellement
La page chaîne essaie maintenant en priorité le chemin officiel déjà présent
dans l'application :
```text
Google OAuth youtube.readonly
↓
channels.list -> playlist uploads
↓
playlistItems.list (pagination de 50)
↓
videos.list (durée + vues + date)
↓
affichage progressif
```
Elle ne s'arrête plus aux 30 vidéos de l'ancienne version et suit
`nextPageToken` jusqu'à la fin de la playlist d'uploads.
Si le compte Google n'est pas disponible, elle bascule sur le chemin public
`youtube_explode_dart.getUploadsFromPage()`.
## 2. Le classement Chaînes utilise MES vues
Le classement précédent utilisait les vues publiques YouTube. C'était une
mauvaise interprétation de la demande.
Nouveau classement :
```text
#1 chaîne avec le plus de lectures dans YouTube Personal
#2 ...
#3 ...
```
Chaque ouverture réelle d'une vidéo incrémente un compteur local :
```text
youtube_personal.watch_counts.v1
```
Un changement 1080p -> 4K ne compte PAS comme une nouvelle vue.
Les anciennes entrées de l'historique sont migrées avec au moins 1 vue par
vidéo, car l'application ne peut pas reconstruire les répétitions passées
qu'elle n'enregistrait pas encore.
Carte exemple :
```text
12 de mes vues • 4 vidéos différentes
La plus regardée par moi (5×) : ...
```
## 3. Une chaîne ne m'intéresse pas
Menu `...` d'une chaîne :
```text
Cette chaîne ne m'intéresse pas
```
La chaîne est alors :
```text
- retirée du menu Chaînes
- exclue des prochaines recommandations Radar
- mémorisée localement
```
Un bouton ANNULER apparaît immédiatement après le masquage.
La page complète de la chaîne possède aussi :
```text
Ne plus me proposer cette chaîne
```
L'historique existant n'est pas détruit.
## 4. Sponsors intégrés aux vidéos
Ajout de SponsorBlock, catégorie `sponsor` uniquement.
YouTube Personal récupère les segments connus et, lorsque la lecture entre
dans l'un d'eux :
```text
lecture normale
↓
début sponsor détecté
↓
seek automatique à la fin du sponsor
↓
lecture continue
```
Notification courte :
```text
Sponsor ignoré automatiquement • 34s
```
Le mécanisme fonctionne dans le lecteur Windows/Android et aussi dans le
Player audio Android lorsque l'écran est verrouillé.
### Vie privée SponsorBlock
Le client utilise l'endpoint k-anonymity : il calcule SHA-256(videoId) et
n'envoie au serveur que les 4 premiers caractères du hash. Il filtre ensuite
localement la réponse pour retrouver la vidéo exacte.
Seule la catégorie `sponsor` est ignorée. Les intros, outros, rappels de
s'abonner et autres catégories ne sont pas sautés.
## Test
Extraire le ZIP à la racine puis :
Windows :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Android :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d f10778d8
```
Pas besoin de `flutter clean`.
### Vérifications
1. Menu Chaînes : le texte doit afficher `mes vues`, plus les millions de vues
publiques.
2. Ouvrir deux fois une vidéo, revenir dans Chaînes : le compteur augmente.
3. Changer seulement la qualité : le compteur ne doit pas augmenter.
4. Cliquer une chaîne : les vidéos doivent commencer à apparaître.
5. Tester `Plus vues sur YouTube` dans la page de la chaîne.
6. Masquer une chaîne puis vérifier qu'elle disparaît du Radar.
7. Sur une vidéo connue de SponsorBlock, chercher le log :
```text
[SPONSORBLOCK] ... segment(s) sponsor trouvé(s)
[SPONSORBLOCK] skip ...
```
+101
View File
@@ -0,0 +1,101 @@
# Étape 18.5.1 - Infos chaîne visibles sans compte Google
Le log a révélé la cause :
```text
[CHANNEL] Data API indisponible -> fallback public:
Bad state: Aucune session Google active.
```
La 18.5 récupérait la fiche statistique détaillée uniquement après obtention
d'un access token Google. Le catalogue avait un fallback public, mais pas la
fiche d'informations.
La 18.5.1 corrige cela.
## Sur la page vidéo
Sous le nom de la chaîne apparaissent maintenant directement :
```text
2,4 M abonnés
486 M vues chaîne
Créée 14 mars 2016
84 k likes vidéo
```
Ces données sont chargées sans quitter la vidéo et sans nécessiter Google
OAuth.
`likes vidéo` désigne explicitement les likes de la vidéo actuelle, car
YouTube ne fournit pas un compteur global de likes au niveau Channel.
## Quand on ouvre la chaîne
La fiche complète est maintenant alimentée en deux niveaux :
```text
fallback public youtube_explode_dart
↓
si Google OAuth existe :
YouTube Data API remplace/enrichit les données
```
Donc même déconnecté du compte Google, la fiche peut afficher :
```text
nom
photo
description
pays
date de création / date d'arrivée
abonnés
vues totales de la chaîne
```
Les likes des vidéos sont également récupérés par le chemin public et sont
additionnés progressivement dans la fiche :
```text
Likes chargés
↓
Likes cumulés
```
## Important sur les likes
Il n'existe pas de `statistics.likeCount` global pour une ressource Channel.
Le total affiché dans la fiche chaîne est donc calculé par YouTube Personal à
partir des `likeCount` publics des vidéos chargées.
## Test
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Test immédiat :
```text
ouvrir une vidéo
↓
regarder sous le nom de la chaîne
```
Les petites pastilles de statistiques doivent apparaître sans avoir besoin de
cliquer sur la chaîne.
Puis :
```text
cliquer sur la chaîne
```
La fiche complète doit apparaître même si le log indique qu'aucune session
Google n'est active.
+105
View File
@@ -0,0 +1,105 @@
# Étape 18.5.2 - Correctif réel des statistiques de chaîne Windows
La capture 18.5.1 montre que seule cette donnée arrivait :
```text
45 k likes vidéo
```
Cela prouve que la métadonnée de la vidéo fonctionne, mais que les champs de
la chaîne (abonnés / vues / date) revenaient vides par le chemin public
`youtube_explode_dart`.
## Nouveau fallback Windows
YouTube Personal réutilise maintenant le `yt-dlp.exe` déjà fourni avec le
lecteur HD.
Pour une chaîne :
```text
youtube_explode_dart
↓
si champs manquants
↓
yt-dlp sur /channel/<id>/videos
↓
si nécessaire /about
↓
fusion des métadonnées
```
Champs récupérés quand YouTube les expose :
```text
channel_follower_count -> abonnés
view_count -> vues chaîne
playlist_count -> vidéos
channel/title -> nom
description -> description
thumbnails -> avatar
```
## Logs
Le test doit maintenant écrire une ligne très claire :
```text
[CHANNEL-YTDLP][18.5.2] channel=UC... subs=... views=... videos=...
[CHANNEL-META][18.5.2] id=UC... subs=... views=... videos=... date=...
```
Cela évite désormais les échecs silencieux.
## Sous la vidéo
La zone peut maintenant afficher :
```text
2,4 M abonnés
486 M vues chaîne
312 vidéos
45 k likes vidéo
```
Si YouTube ne fournit réellement aucune statistique de chaîne, l'interface
affiche explicitement :
```text
Stats chaîne indisponibles
```
au lieu de faire croire que le code n'a pas été exécuté.
## Date
La vraie date de création reste utilisée dès qu'elle est fournie par la page
À propos ou par YouTube Data API.
Si cette date n'est pas publiquement extractible mais que le catalogue complet
est chargé, la page chaîne affiche à la place une donnée honnête :
```text
Plus ancienne vidéo : 14/03/2017
```
Elle n'est PAS présentée comme la date de création de la chaîne.
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Ouvrir la même vidéo Gamerawi et regarder :
```text
sous le nom de la chaîne
```
Puis regarder les lignes `[CHANNEL-YTDLP][18.5.2]` dans PowerShell.
+105
View File
@@ -0,0 +1,105 @@
# Étape 18.5 - Informations détaillées de chaîne
Ajout d'une vraie fiche d'informations en haut de la page d'une chaîne.
## Informations affichées
```text
Nom
Photo
Description
Pays
Date de création
Nombre d'abonnés
Nombre total de vues de la chaîne
Nombre de vidéos publiques
Likes cumulés des vidéos chargées
Commentaires cumulés des vidéos chargées
```
Les statistiques générales de chaîne viennent de `channels.list` avec :
```text
part=snippet,statistics
```
La date affichée est la date de création/publication de la chaîne
(`snippet.publishedAt`).
## Likes
YouTube Data API n'expose pas de `likeCount` global dans les statistiques
d'une chaîne.
Le champ `likeCount` existe par contre pour les vidéos.
YouTube Personal additionne donc :
```text
likes vidéo 1
+ likes vidéo 2
+ likes vidéo 3
+ ...
```
Au cours du chargement, l'interface affiche :
```text
Likes chargés
```
Quand le catalogue est terminé :
```text
Likes cumulés
```
avec le nombre de vidéos réellement mesurées.
Même principe pour les commentaires.
## Vidéos
Chaque VideoItem peut maintenant mémoriser :
```text
viewCount
likeCount
commentCount
uploadDate
```
La ligne sous une vidéo de chaîne peut donc afficher :
```text
1,2 M vues YouTube • 84 k likes • 3,2 k commentaires • 15/08/2026
```
## Test
Après extraction :
Windows :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Android :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d f10778d8
```
Pas besoin de `flutter clean`.
Ouvrir :
```text
Chaînes
→ une chaîne
```
La fiche statistique doit apparaître au-dessus du tri des vidéos.
+35
View File
@@ -0,0 +1,35 @@
# Étape 18.6.1 - Correctif compilation `Colors.white45`
Erreur corrigée :
```text
lib/widgets/video_result_card.dart(...):
Member not found: 'white45'
```
Cause :
Flutter ne fournit pas de constante `Colors.white45`.
Correction :
```dart
Colors.white45
```
devient :
```dart
const Color(0x73FFFFFF)
```
Cela conserve environ 45 % d'opacité sans changer le reste de l'étape 18.6.
Test :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
+62
View File
@@ -0,0 +1,62 @@
# Étape 18.6.2 - Navigation des listes horizontales
La capture montrait que les cartes continuaient hors écran mais qu'il n'y
avait aucun contrôle visible pour accéder à la suite avec une souris.
Toutes les rangées de l'accueil utilisent maintenant le même composant.
## Windows / desktop
Chaque rangée affiche automatiquement :
```text
← →
```
quand il y a du contenu caché à gauche ou à droite.
Le bouton droit avance d'environ 82 % de la largeur visible, ce qui donne un
effet de carrousel et laisse un peu de contexte avec la page précédente.
Ajout aussi de :
```text
barre de défilement horizontale
glisser à la souris
trackpad
```
Les flèches disparaissent automatiquement lorsqu'on atteint le début ou la
fin.
## Sections corrigées
```text
Pour toi maintenant
Continuer ce sujet
Quelque chose de différent
Explorer par catégorie
Continuer à regarder
À redécouvrir
Vues récemment
Favoris récents
```
## Android
Pas de boutons superposés sur petit écran. Le swipe horizontal natif reste
utilisé.
## Test
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Sur `Pour toi maintenant`, une flèche `>` doit apparaître à droite dès que
la rangée contient plus de cartes que la largeur de l'écran.
+62
View File
@@ -0,0 +1,62 @@
# Étape 18.6 - Infos dans les aperçus + description vidéo
La fiche vidéo affichait déjà les statistiques de chaîne, mais les cartes du
Radar n'en montraient pas assez. La description de la vidéo manquait aussi.
## Cartes Radar
Sous la chaîne :
```text
1,9 M abonnés • 245 k vues • 40 k likes
```
Puis, quand la recherche YouTube fournit le texte :
```text
extrait de la description de la vidéo…
```
Le nombre d'abonnés utilise un chemin léger mis en cache. Le Radar ne lance
pas yt-dlp pour chaque miniature.
## Page vidéo
Sous les statistiques chaîne :
```text
245 k vues • 40 k likes • 1,2 k commentaires • 19/08/2026
Description de la vidéo...
Afficher plus
```
Trois lignes par défaut, puis bouton `Afficher plus`.
## Recherche et page chaîne
Les cartes classiques affichent désormais aussi :
```text
vues • likes
description sur 2 lignes
```
## Persistance
`VideoItem` mémorise maintenant `description`. Les anciens historiques restent
compatibles avec une description vide.
## Correctif
Le badge `0 vidéos` n'est plus affiché lorsqu'yt-dlp n'a pas réellement fourni
le nombre de vidéos de la chaîne.
## Test
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
+139
View File
@@ -0,0 +1,139 @@
# Étape 18.7.1 - Fix blocage / rate-limit / overflow
Le log a révélé deux problèmes différents.
## 1. Rate limit YouTube
Erreur :
```text
RequestLimitExceededException
Failed to perform an HTTP request to YouTube because of rate limiting.
```
La cause venait de la 18.7 : pour une chaîne de 30 vidéos, elle lançait
`videos.get()` sur chaque vidéo, par lots. Cela crée une rafale de requêtes
vers les pages watch YouTube.
Correction :
```text
AVANT
liste chaîne
→ videos.get(video1)
→ videos.get(video2)
→ videos.get(video3)
→ ...
→ IP rate-limitée
```
devient :
```text
MAINTENANT
1 requête catalogue de chaîne
+
1 processus yt-dlp flat-playlist Windows
→ métadonnées groupées
```
Le chemin par vidéo a été supprimé.
Log attendu :
```text
[CHANNEL-VIDEOS][18.7.1] ... mode=NO_PER_VIDEO_WATCH_REQUESTS
[CHANNEL-CATALOG][18.7.1] bulk start ...
[CHANNEL-CATALOG][18.7.1] bulk ready ... videos=...
```
## 2. Le lecteur faisait aussi une requête de trop
À l'ouverture d'une vidéo, la page avait déjà un `VideoItem` issu du Radar,
de la recherche ou de la chaîne, mais faisait quand même un nouveau
`videos.get(videoId)` uniquement pour récupérer description/statistiques.
Cette requête supplémentaire est supprimée.
La page utilise maintenant immédiatement `widget.video`.
## 3. Tri
En fallback public, changer :
```text
Plus récentes
Plus vues
Plus anciennes
```
relance maintenant l'ordre natif de la chaîne.
Donc même si certaines métadonnées de vue sont absentes, le tri n'est plus
un simple tri local de valeurs nulles/0.
`Plus aimées` reste un tri local quand des likes sont réellement disponibles.
## 4. Métadonnées groupées Windows
Un nouveau service utilise le yt-dlp déjà fourni :
```text
--flat-playlist --dump-json
/channel/<id>/videos
```
Il tente de récupérer en un seul passage :
```text
durée
vues
likes si exposés
commentaires si exposés
date
description si exposée
```
Puis fusionne ces données avec les cartes déjà visibles.
Si yt-dlp ne fournit pas une valeur, la page ne montre plus un faux
`0 vues`.
## 5. Overflow accueil
Erreur :
```text
A RenderFlex overflowed by 5.1 pixels on the bottom
```
La barre de défilement horizontale prenait quelques pixels sur la hauteur des
cartes ordinaires. La hauteur du rail a été augmentée pour laisser la place.
## Important
Le rate-limit est imposé côté YouTube. Ce correctif arrête d'en créer de
nouveaux, mais un blocage déjà actif peut mettre un peu de temps à disparaître
côté YouTube.
Le chemin HD yt-dlp n'a pas été modifié.
## Test
Extraire le ZIP puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Ensuite :
1. ouvrir `Chaînes` ;
2. ouvrir Gamerawi ;
3. vérifier les logs `18.7.1` ;
4. changer `Plus récentes` → `Plus vues` → `Plus anciennes` ;
5. ouvrir ensuite une vidéo et vérifier que le log ne produit plus une rafale
de `RequestLimitExceededException`.
+58
View File
@@ -0,0 +1,58 @@
# Étape 18.7.2 - Restauration lecture Windows
Le log montre que le lecteur Windows échoue AVANT le pipeline yt-dlp :
```text
[YT-PLAYER] RequestLimitExceededException
Request: GET https://www.youtube.com/watch?v=...
```
## Cause
`_loadVideoInternal()` appelait toujours d'abord :
```text
youtube_explode_dart -> getPlayableMuxedUrl() -> /watch
```
Comme l'IP est temporairement rate-limitée sur ce endpoint, le code quittait
avant même d'essayer le chemin Windows yt-dlp/web_embedded.
## Correction
Windows démarre maintenant directement :
```text
WindowsHdService
-> yt-dlp web_embedded
-> cache progressif local
-> petit prébuffer
-> media_kit
```
Le chemin Windows initial ne dépend donc plus du flux 360 de
`youtube_explode_dart`.
Android conserve son chemin actuel.
Les informations de chaîne sont aussi lancées APRÈS la tentative de lecture
Windows pour ne pas concurrencer le démarrage vidéo.
## Logs attendus
```text
[HD-WINDOWS][18.7.2] yt-dlp-only start ...
[HD-WINDOWS][18.7.2] prébuffer prêt ...
[HD-WINDOWS][18.7.2] lecture confirmée ...
```
## Test
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Tester en premier la même vidéo Gamerawi qui échoue actuellement.
+128
View File
@@ -0,0 +1,128 @@
# Étape 18.7 - Page chaîne riche + tri réellement fonctionnel
La capture montrait deux problèmes distincts :
```text
durée 0:00
0 vues
```
et une grande zone vide à droite.
Le tri local utilisait donc des valeurs identiques (0 vues / dates absentes),
ce qui explique pourquoi "Plus récentes", "Plus vues" et "Plus anciennes"
semblaient ne rien changer.
## Correction des métadonnées
La liste de chaîne donne d'abord les IDs des uploads.
YouTube Personal enrichit maintenant chaque vidéo avec :
```text
youtube.videos.get(videoId)
```
pour récupérer la vraie fiche :
```text
durée
vues
likes
date de publication
description
```
L'enrichissement se fait par lots de 6 pour ne pas lancer toutes les requêtes
en même temps.
## Tri
Le menu devient :
```text
Plus récentes
Plus vues
Plus aimées
Plus anciennes
```
Les tris utilisent désormais les vraies métadonnées.
Pour le fallback public, la demande de chaîne utilise également le tri natif :
```text
VideoSorting.newest
VideoSorting.popularity
VideoSorting.oldest
```
## Nouvelle présentation desktop
Au lieu d'une petite ligne collée à gauche avec beaucoup de vide :
```text
[ miniature ] texte [ grand vide ]
```
la page utilise maintenant une grille responsive :
```text
[ vidéo 1 ] [ vidéo 2 ] [ vidéo 3 ] [ vidéo 4 ]
[ vidéo 5 ] [ vidéo 6 ] [ vidéo 7 ] [ vidéo 8 ]
```
Selon la largeur :
```text
>= 1500 px : 4 colonnes
>= 1080 px : 3 colonnes
>= 720 px : 2 colonnes
mobile : 1 colonne
```
Chaque carte affiche :
```text
miniature
durée
titre
vues
likes
commentaires si disponibles
date de publication
description
```
## Résumé du catalogue
Au-dessus du tri apparaissent maintenant :
```text
Vidéos chargées
Vues cumulées
Moyenne vues / vidéo
Likes cumulés
Durée totale chargée
Période des vidéos
```
## Test
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Dans PowerShell, le fallback public doit aussi afficher :
```text
[CHANNEL-VIDEOS][18.7] channel=UC... sort=newest uploads=...
```
Puis vérifier visuellement que `Plus vues` place réellement les vidéos les
plus vues en premier et que `Plus anciennes` inverse clairement les dates.
+118
View File
@@ -0,0 +1,118 @@
# Étape 18.8.1 - Finance devient une catégorie du Radar
Correction d'interprétation de l'étape 18.8.
## Besoin réel
Finance est un centre d'intérêt souhaité.
Il doit donc :
```text
rester dans le profil du Radar
+
pouvoir apparaître dans Signal actuel
+
générer des recommandations
+
avoir sa propre catégorie
```
Il ne doit PAS être bloqué.
## Nouvelle catégorie
Ajout :
```text
Finance
```
avec détection de termes comme :
```text
finance
investissement
bourse
actions
ETF
IPO
dividendes
trading
forex
crypto
bitcoin
banque
épargne
portefeuille
Nasdaq
S&P 500
CAC 40
MASI
BVC
...
```
et plusieurs équivalents arabes.
## Ce qui est conservé de la 18.8
Le bouton `×` sur les centres d'intérêt reste utile.
Il permet toujours d'exclure MANUELLEMENT un sujet qui ne t'intéresse pas.
Exemple :
```text
[gamer ×] [ovni ×] [football ×]
```
Mais Finance n'est plus une exclusion par défaut.
## Migration
Si la version 18.8 avait laissé un terme Finance dans les exclusions pendant
les essais, la 18.8.1 nettoie automatiquement le vocabulaire Finance de cette
liste.
Aucun historique, favori ou compteur de vues n'est supprimé.
## Résultat attendu
Dans `Explorer par catégorie`, Finance peut apparaître comme :
```text
[Finance (7)]
```
et regrouper uniquement les recommandations financières détectées.
Le `Signal actuel` peut également contenir à nouveau des termes comme :
```text
finance
bourse
investissement
ETF
```
si l'historique et les favoris montrent réellement cet intérêt.
## Test
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Puis :
```text
Accueil
→ Mélanger mon Radar
```
Finance doit maintenant être traitée comme un intérêt positif et non comme un
sujet interdit.
+64
View File
@@ -0,0 +1,64 @@
# Étape 18.8.2 - Correctif compilation Finance
Erreur corrigée :
```text
radar_service.dart(41,...)
A named parameter that doesn't refer to an instance variable can't start with an underscore
Type 'await' not found
No named parameter with the name 'mode'
```
## Cause
L'appel :
```dart
await _restoreFinanceInterestIfNeeded();
```
avait été inséré par erreur **dans la liste des paramètres nommés** de :
```dart
generate({...})
```
au lieu d'être placé dans le corps de la méthode.
## Correction
La signature redevient :
```dart
Future<RadarFeed> generate({
required RadarSessionMode mode,
bool force = false,
}) async {
await _restoreFinanceInterestIfNeeded();
...
}
```
Le paramètre `mode` redevient donc valide et les erreurs en cascade de
`home_page.dart` disparaissent également.
## Migration Finance améliorée
Le nettoyage des exclusions Finance provenant de la mauvaise étape 18.8 est
maintenant exécuté **une seule fois**.
Cela évite qu'un futur clic manuel sur le `×` de Finance soit automatiquement
annulé à chaque rafraîchissement du Radar.
La catégorie Finance reste bien présente et positive.
## Test
Extraire le ZIP à la racine puis :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
+93
View File
@@ -0,0 +1,93 @@
# Étape 18.9.1 - Retour aux centres d'intérêt fixes en haut
Cette version annule les modifications 18.8 / 18.9 qui avaient transformé
les mots extraits des titres en pseudo-centres d'intérêt.
## Base restaurée
Les deux fichiers concernés repartent de la dernière base stable avant cette
régression :
```text
radar_service.dart -> base 18.4
home_page.dart -> base 18.7.1
```
Aucun fichier du lecteur, de la HD Windows, d'Android, des chaînes ou de
SponsorBlock n'est remplacé.
## Barre des centres d'intérêt
La rangée fixe est de nouveau en haut :
```text
Tous
Politique
Finance
Technologie & IA
Jeux vidéo
Culture & histoire
Science & espace
OVNI & mystères
Auto & transport
Musique
Société & actualité
Tutoriels & apprentissage
```
Les termes bruts tels que :
```text
morocco
sky
yann
arthus
2026
```
ne sont plus affichés comme centres d'intérêt.
## Fonctionnement
`Tous` conserve le Radar personnalisé général.
Quand une catégorie est choisie, le Radar ajoute une recherche explicite liée
à ce centre d'intérêt.
Exemple :
```text
Finance
→ finance investissement bourse ETF marchés financiers
Politique
→ politique géopolitique actualité gouvernement élections
```
Le moteur garde aussi une partie du profil historique afin de personnaliser
les résultats à l'intérieur du thème choisi.
## Catégories
`Politique` est séparée de `Société & actualité`.
`Finance` est une catégorie positive dédiée.
## Interface
Le bloc dynamique inférieur `Explorer par catégorie` est retiré afin d'éviter
d'avoir deux systèmes de catégories différents sur le même écran.
La rangée du haut redevient le point de contrôle principal des centres
d'intérêt.
## Test
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
+171
View File
@@ -0,0 +1,171 @@
# Étape 18.9 - Restauration des vrais centres d'intérêt du Radar
La capture montrait une régression importante :
```text
morocco
sky
yann
arthus
bertrand
2026
```
étaient affichés comme des « centres d'intérêt ».
Ce ne sont pas des centres d'intérêt : ce sont seulement des mots extraits de
titres.
## Séparation restaurée
Le Radar a maintenant deux niveaux distincts :
```text
CENTRES D'INTÉRÊT SÉMANTIQUES
↓
Jeux vidéo
Culture & histoire
OVNI & mystères
Technologie & IA
Science & espace
Auto & transport
Musique
Finance
Société & actualité
Tutoriels & apprentissage
MOTS-CLÉS FINS INTERNES
↓
morocco
playstation
bitcoin
android
roswell
etc.
```
Les mots-clés fins restent utiles au calcul, mais ils ne sont plus présentés
comme le profil principal de l'utilisateur.
## Signal visible
La zone en haut de l'accueil devient :
```text
Centres d’intérêt détectés :
[Culture & histoire ×]
[Technologie & IA ×]
[Finance ×]
[Jeux vidéo ×]
...
```
et non :
```text
[morocco ×] [sky ×] [yann ×] [2026 ×]
```
## Calcul du profil
Chaque vidéo de l'historique reçoit :
```text
poids de récence
+
classification sémantique
```
Les favoris reçoivent un poids plus fort.
Les catégories sont ensuite classées selon leur poids cumulé.
## Radar
Les recherches de nouvelles vidéos partent désormais d'abord des centres
d'intérêt.
Exemple :
```text
Finance
→ finance investissement bourse ETF marchés financiers
Culture & histoire
→ documentaire culture histoire civilisation
Technologie & IA
→ technologie intelligence artificielle informatique
```
Une seule partie des recherches utilise encore des mots précis issus des
titres récents.
Cela évite qu'une série de vidéos contenant « Morocco » ou le nom d'un auteur
transforme tout le Radar en recommandations sur ce seul mot.
## Scoring
Le score d'une recommandation comprend maintenant explicitement :
```text
Centre d’intérêt
Détails
Habitudes
Favoris
Découverte
Session
```
Le centre d'intérêt sémantique peut apporter jusqu'à 28 points.
Les mots bruts ont été abaissés à un maximum de 22 points afin qu'un nom
propre répété ne domine plus le profil.
## Classification
La détection utilise maintenant :
```text
titre
+
nom de chaîne
+
description
```
au lieu de seulement titre + chaîne.
## Exclusion avec ×
Le `×` agit maintenant sur toute la catégorie.
Exemple :
```text
[Finance ×]
```
exclut les vidéos classifiées Finance du profil et des nouvelles
recommandations.
Finance reste toutefois une catégorie positive par défaut.
## Test
Après extraction :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Puis :
```text
Accueil
→ Mélanger mon Radar
```
La ligne du haut doit afficher des catégories sémantiques et plus les mots
bruts `morocco / sky / yann / 2026`.
+114
View File
@@ -0,0 +1,114 @@
# Étape 18 - Radar V1
Première version du système original de vidéos proposées.
## Principe
Le classement est calculé localement à partir de :
- historique ;
- favoris ;
- chaînes déjà regardées ;
- sujets extraits des titres ;
- durée de la vidéo ;
- mode de session choisi ;
- feedback `Pas maintenant` ;
- feedback `Moins de cette chaîne`.
YouTube sert seulement à fournir des vidéos candidates via les recherches
déjà utilisées par l'application. Le score final et l'ordre sont calculés par
YouTube Personal.
## Nouveautés de l'accueil
### Radar personnel
Sections :
```text
Pour toi maintenant
Continuer ce sujet
Quelque chose de différent
À redécouvrir
Continuer à regarder
Vues récemment
Favoris récents
```
### Modes de session
```text
Comme d'habitude
Je veux du nouveau
Apprendre
Détente
Courtes vidéos
Long format
```
Le mode ne modifie pas définitivement le profil. Il influence seulement la
génération actuelle du Radar.
### Mélanger mon Radar
Force une nouvelle combinaison de candidats et de classement.
### Pourquoi cette vidéo ?
Chaque proposition possède un score sur 100 et une explication :
```text
Proche de ce que tu regardes maintenant
Thèmes communs : ...
Chaîne proche de tes habitudes
Sujet proche de tes favoris
Nouvelle chaîne pour toi
```
La fenêtre affiche aussi la contribution du score :
```text
Sujet +...
Habitudes +...
Favoris +...
Découverte +...
Session +...
Feedback -...
```
### Feedback
Menu `...` de chaque proposition :
```text
Pourquoi cette vidéo ?
Pas maintenant
Moins de cette chaîne
```
`Pas maintenant` masque la vidéo pendant 30 jours.
`Moins de cette chaîne` ajoute un malus persistant à cette chaîne.
## Fichiers
```text
lib/models/radar_recommendation.dart
lib/services/radar_service.dart
lib/screens/home_page.dart
```
## Test
Extraire le ZIP à la racine :
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter run -d windows
```
Pas besoin de `flutter clean`.
Le premier chargement du Radar peut prendre quelques secondes car il lance
plusieurs recherches candidates. Les générations suivantes sont gardées en
cache mémoire tant que l'historique/favoris/feedback ne changent pas.
+143
View File
@@ -0,0 +1,143 @@
# Google OAuth + abonnements YouTube
Cette étape permet de connecter un compte Google et de charger les
abonnements YouTube dans l'onglet **Abonnements**.
L'application utilise uniquement le scope :
`https://www.googleapis.com/auth/youtube.readonly`
Aucun mot de passe Google n'est demandé ou stocké par l'application.
---
## 1. Créer le projet Google Cloud
Dans Google Cloud Console :
1. crée ou sélectionne un projet ;
2. ouvre **APIs & Services > Library** ;
3. active **YouTube Data API v3** ;
4. configure l'écran de consentement OAuth ;
5. si l'application est encore en mode **Testing**, ajoute ton propre
compte Google dans **Test users**.
---
## 2. Android
Package Android :
`com.studioweb.youtube_personal`
### Trouver le SHA-1 debug
Dans PowerShell :
```powershell
cd "D:\07-Logiciels\23-youtube_personal\android"
.\gradlew signingReport
```
Cherche :
```text
Variant: debug
SHA1: ...
```
### Créer les identifiants
Dans **Google Cloud > APIs & Services > Credentials** :
1. crée un **OAuth client ID > Android** ;
2. Package name :
`com.studioweb.youtube_personal`
3. SHA-1 :
colle le SHA-1 obtenu avec `signingReport`.
Ensuite crée aussi :
**OAuth client ID > Web application**
Copie le Client ID qui se termine normalement par :
`apps.googleusercontent.com`
### Dans YouTube Personal
1. ouvre **Abonnements** ;
2. clique **Configurer** ;
3. colle le **Client ID Web** ;
4. clique **Enregistrer** ;
5. clique **Se connecter avec Google**.
Le Client ID du client Android n'est pas collé dans l'application.
Il sert à autoriser le package + la signature APK côté Google.
---
## 3. Windows
Dans **Credentials** crée :
**OAuth client ID > Desktop app**
Dans YouTube Personal :
1. ouvre **Abonnements** ;
2. clique **Configurer** ;
3. colle le **Client ID Desktop** ;
4. colle le Client Secret si Google en fournit un ;
5. enregistre ;
6. clique **Se connecter avec Google**.
Le navigateur système s'ouvre. Après autorisation, Google revient sur
une adresse temporaire locale `127.0.0.1`. Aucun serveur externe n'est
nécessaire.
---
## 4. Si Google affiche une erreur
### Android : erreur de configuration / annulation après choix du compte
Vérifie en priorité :
- package exact : `com.studioweb.youtube_personal`
- SHA-1 debug correct
- client OAuth Android créé dans le même projet
- Client ID Web correct dans l'application
- ton compte présent dans **Test users** si l'écran OAuth est en Testing
### Erreur YouTube 403
Vérifie que **YouTube Data API v3** est activée dans le même projet.
### Windows : redirect_uri_mismatch
Utilise bien un client OAuth de type **Desktop app** et non Web
application.
---
## 5. Alternative avec dart-define
Les anciens paramètres restent compatibles :
### Android
```powershell
flutter run -d f10778d8 `
--dart-define=GOOGLE_WEB_CLIENT_ID="TON_CLIENT_ID_WEB.apps.googleusercontent.com"
```
### Windows
```powershell
flutter run -d windows `
--dart-define=GOOGLE_DESKTOP_CLIENT_ID="TON_CLIENT_ID_DESKTOP.apps.googleusercontent.com" `
--dart-define=GOOGLE_DESKTOP_CLIENT_SECRET="TON_CLIENT_SECRET"
```
Les valeurs `dart-define` ont priorité sur celles enregistrées dans l'app.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+48
View File
@@ -0,0 +1,48 @@
# YouTube Personal — étape 18.9.2 — Icône application
Ce patch conserve les fichiers de la version 18.9.1 (barre Tous / Politique / Finance / Technologie & IA...) et ajoute une icône d'application pour Windows et Android.
## Fichiers ajoutés
- `windows/runner/resources/app_icon.ico` : icône de l'exécutable Windows
- `android/app/src/main/res/mipmap-*/ic_launcher.png` : icône Android
- `android/app/src/main/res/mipmap-*/ic_launcher_round.png` : variante ronde Android
- `assets/app_icon/app_icon_1024.png` : source haute résolution
- `ICON_PREVIEW.png` : aperçu
## Installation du patch
Extraire ce ZIP directement à la racine du projet `D:\07-Logiciels\23-youtube_personal` et accepter le remplacement des fichiers.
## Windows
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter clean
flutter pub get
flutter run -d windows
```
Pour générer une version Windows release :
```powershell
flutter build windows --release
```
## Android — téléphone branché en USB avec débogage USB activé
```powershell
cd "D:\07-Logiciels\23-youtube_personal"
flutter devices
flutter clean
flutter pub get
flutter run -d f10778d8
```
Si l'identifiant du téléphone a changé, remplace `f10778d8` par l'identifiant affiché par `flutter devices`.
Pour fabriquer et installer l'APK release :
```powershell
flutter build apk --release
adb install -r "build\app\outputs\flutter-apk\app-release.apk"
```
Si `adb` n'est pas reconnu, tu peux installer avec Flutter :
```powershell
flutter install -d f10778d8
```
+13
View File
@@ -0,0 +1,13 @@
$ErrorActionPreference = "Stop"
$ProjectRoot = $PSScriptRoot
$apk = Join-Path $ProjectRoot "build\app\outputs\flutter-apk\app-release.apk"
if (-not (Test-Path $apk)) {
throw "APK introuvable. Lance d'abord .\BUILD_APK_RELEASE.ps1"
}
Write-Host "=== Appareils ADB ===" -ForegroundColor Cyan
adb devices
Write-Host ""
Write-Host "=== Installation ===" -ForegroundColor Cyan
adb install -r "$apk"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
Write-Host "[OK] Application installee." -ForegroundColor Green
+15
View File
@@ -0,0 +1,15 @@
$ErrorActionPreference = "Stop"
$ProjectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$Pubspec = Join-Path $ProjectRoot "pubspec.yaml"
$Version = "1.0.0"
if (Test-Path $Pubspec) {
$Text = Get-Content $Pubspec -Raw
if ($Text -match '(?m)^version:\s*([^\s+]+)(?:\+\S+)?\s*$') {
$Version = $Matches[1].Trim()
}
}
$Setup = Join-Path $ProjectRoot "dist\YouTube_Personal_Setup_$Version.exe"
if (-not (Test-Path $Setup)) {
throw "Installateur introuvable : $Setup`nLance d'abord .\CREER_INSTALLATEUR_WINDOWS.ps1"
}
Start-Process -FilePath $Setup -Verb RunAs
+78
View File
@@ -0,0 +1,78 @@
# Configuration Google / YouTube – Abonnements
L'application n'embarque volontairement aucun identifiant OAuth personnel.
## 1. Google Cloud
1. Crée ou sélectionne un projet Google Cloud.
2. Active **YouTube Data API v3**.
3. Configure l'écran de consentement OAuth.
4. Si l'application est encore en mode **Testing**, ajoute ton compte Google
dans les utilisateurs de test.
L'application demande uniquement :
`https://www.googleapis.com/auth/youtube.readonly`
Ce scope permet de lire les données de ton compte YouTube, notamment les
abonnements.
---
## 2. Android
Identifiant de package :
`com.studioweb.youtube_personal`
### Récupérer le SHA-1 debug
Dans PowerShell, depuis le projet :
```powershell
cd "D:\07-Logiciels\23-youtube_personal\android"
.\gradlew signingReport
```
Repère `Variant: debug` puis copie la valeur `SHA1`.
### Créer les clients Google
Dans Google Cloud > APIs & Services > Credentials :
1. Crée un client OAuth **Android** :
- package : `com.studioweb.youtube_personal`
- SHA-1 : celui récupéré ci-dessus.
2. Crée aussi un client OAuth **Web application**.
3. Copie le **Client ID du client Web**.
Lance ensuite Android avec :
```powershell
flutter run -d f10778d8 --dart-define=GOOGLE_WEB_CLIENT_ID="TON_CLIENT_ID_WEB.apps.googleusercontent.com"
```
---
## 3. Windows
Dans Google Cloud > Credentials, crée un client OAuth :
**Desktop app**
Copie son Client ID et, s'il est fourni, son Client Secret.
Lance Windows avec :
```powershell
flutter run -d windows `
--dart-define=GOOGLE_DESKTOP_CLIENT_ID="TON_CLIENT_ID_DESKTOP.apps.googleusercontent.com" `
--dart-define=GOOGLE_DESKTOP_CLIENT_SECRET="TON_CLIENT_SECRET"
```
Sur Windows, le bouton Connexion ouvre le navigateur système. Google redirige
ensuite vers un serveur local temporaire `127.0.0.1` lancé uniquement pendant
la connexion. Il n'y a aucun backend permanent.
Dans cette première version, le jeton Windows n'est pas conservé après la
fermeture de l'application : il faudra se reconnecter à la prochaine session.
+17
View File
@@ -0,0 +1,17 @@
# YouTube Personal 18.7.10 — Android 1440p / 4K exact
## Portée
Correctif strictement Android. Aucun service Windows n'est modifié.
## Correctifs
- Le sélecteur natif Android utilise la hauteur du libellé de résolution (`1440p`, `2160p`) si nécessaire.
- Pour une demande 1440p ou 2160p, la hauteur exacte est prioritaire lorsqu'elle existe.
- Une demande 1440p/2160p ne peut plus être validée silencieusement par un flux 1080p.
- La bascule Android attend maintenant que MediaCodec publie exactement la résolution sélectionnée avant de confirmer la qualité.
- Si la vidéo ne propose pas la qualité demandée, la lecture actuelle continue et un message indique la résolution maximale disponible.
- Ajout de logs `[HD-ANDROID][18.7.10]` avec les hauteurs disponibles et la hauteur réellement sélectionnée.
## Windows
- `windows_hd_service.dart` absent du ZIP.
- Aucun code du pipeline Windows 18.7.5 n'est modifié.
- Les changements Dart sont derrière `Platform.isAndroid`.
+18
View File
@@ -0,0 +1,18 @@
# Patch 18.7.11a — Correctif compilation langue préférée
## Cause
Le patch 18.7.11 avait inclus par erreur une version incomplète de `preferred_language_service.dart`.
## Correctif
Restauration des fonctions déjà utilisées par le projet :
- `labelFor`
- `nativeSearchHint`
- `dubbingSearchHint`
- `scoreVideo`
- `isLikelyDubbed`
- `rankVideos`
## Impact
- Navigation 18.7.11 conservée.
- Android 18.7.10 conservé.
- Aucun fichier du lecteur Windows n'est modifié.
+11
View File
@@ -0,0 +1,11 @@
# Patch 18.7.11b — Correction des overflows d'affichage
## Corrections
- Le mode desktop/rail n'est plus activé sur un téléphone simplement en paysage.
- La page Bibliothèque adapte maintenant le nombre de colonnes (1 / 2 / 3).
- Les cartes Bibliothèque utilisent une hauteur fixe adaptée et un layout interne non débordant.
- Le rail affiche seulement le label sélectionné pour réduire l'encombrement vertical.
## Fichiers modifiés
- lib/main.dart
- lib/screens/library_pages.dart
+15
View File
@@ -0,0 +1,15 @@
# Patch 18.7.11c — Chaînes / Material ancestor
Correction partagée Windows + Android.
Cause:
`ChannelsPage` était utilisée comme route depuis la nouvelle Bibliothèque sans `Scaffold` / `Material` propre.
Le `TextField` de recherche levait alors `No Material widget found`.
Correction:
- `ChannelsPage` possède désormais son propre `Scaffold`.
- Aucun service vidéo ni lecteur n'est modifié.
- Aucun fichier Windows HD / Android HD n'est modifié.
Fichier modifié:
- lib/screens/channels_page.dart
+15
View File
@@ -0,0 +1,15 @@
# Patch 18.7.11d — Navigation retour des sous-pages
## Problème
Après le regroupement dans Bibliothèque, Chaînes, Historique et Favoris sont ouverts via `Navigator.push()`. Sur Windows, aucune commande de retour n'était visible.
## Correction
- Bouton Retour visible en haut de Chaînes quand la page peut être dépilée.
- Bouton Retour visible en haut de Historique et Favoris quand la page peut être dépilée.
- Le bouton n'apparaît pas sur une page racine.
- Le correctif Material de Chaînes (18.7.11c) est conservé.
- Aucun lecteur vidéo ou service HD n'est modifié.
## Fichiers modifiés
- lib/screens/channels_page.dart
- lib/screens/library_pages.dart
+11
View File
@@ -0,0 +1,11 @@
# Patch 18.7.11e — Fermeture automatique du message Chaînes
Correction:
- Le message "ne sera plus proposée" disparaît automatiquement après 3 secondes.
- Le bouton ANNULER reste fonctionnel pendant ce délai.
- Une fermeture forcée via le contrôleur du SnackBar évite les cas où Windows/Flutter laisse le message persistant.
Impact:
- UI Chaînes uniquement.
- Aucun changement dans le lecteur Windows.
- Aucun changement dans le lecteur Android.
+29
View File
@@ -0,0 +1,29 @@
# Patch 18.7.11 — Refonte du menu bas (Version 1)
## Objectif
Passer de 7 onglets serrés à 5 onglets plus propres sur mobile.
## Nouveau menu bas
- Accueil
- Recherche
- Abos
- Bibliothèque
- Réglages
## Bibliothèque
Le nouvel écran **Bibliothèque** regroupe :
- Historique
- Favoris
- Chaînes
Il affiche aussi des cartes d'accès rapide et des aperçus récents.
## Impact Windows
Aucun impact sur le lecteur Windows ni sur `windows_hd_service.dart`.
## Fichiers modifiés
- lib/main.dart
- lib/screens/library_pages.dart
- lib/screens/settings_page.dart
- lib/services/app_settings_service.dart
- lib/services/preferred_language_service.dart
+23
View File
@@ -0,0 +1,23 @@
# Patch 18.7.12a — Filtre Strict appliqué à tout l'accueil
## Cause
Le filtre 18.7.12 était appliqué aux candidats du Radar, mais pas à toutes les sources visibles de l'accueil.
Des vidéos déjà présentes dans l'historique ou un feed en mémoire pouvaient donc rester visibles dans :
- Continuer à regarder
- Continuer ce sujet
- Découverte
- catégories Radar
- redécouverte
## Correction
- filtrage local juste avant affichage dans HomePage
- filtrage de "Continuer à regarder"
- filtrage de "Continuer ce sujet", "Découverte" et catégories, même avec un ancien feed en mémoire
- filtrage des vidéos qui servent à construire le profil Radar
- ajout de plusieurs chaînes/marques typiquement très jeunes au niveau Strict, dont VLAD ARA
## Non modifié
- recherche manuelle
- lecteur Windows
- lecteur Android
- pipeline HD
+41
View File
@@ -0,0 +1,41 @@
# Patch 18.7.12 — Filtre contenu très jeune
## Nouveau réglage
Paramètres > Filtre contenu très jeune
Niveaux :
- Désactivé
- Modéré
- Strict
## Portée
Le filtre agit sur le Radar et les recommandations automatiques.
Il ne masque pas les résultats d'une recherche explicite de l'utilisateur.
## Détection
Le filtre utilise uniquement les métadonnées textuelles disponibles :
- nom de chaîne
- titre vidéo
- description
- marqueurs textuels multilingues (kids, enfants, bébés, jouets, comptines, etc.)
- quelques marques/chaînes typiquement orientées jeune public
Aucune analyse du visage ou estimation d'âge à partir des miniatures.
## Comportement
- Modéré : exige des signaux forts/explicites.
- Strict : seuil plus bas et donc filtrage plus agressif.
- Toute modification du réglage reconstruit automatiquement le Radar.
## Plateformes
- Windows : oui, recommandations uniquement
- Android : oui, recommandations uniquement
- Lecteur Windows : non modifié
- Lecteur Android : non modifié
## Fichiers
- lib/screens/home_page.dart
- lib/screens/settings_page.dart
- lib/services/app_settings_service.dart
- lib/services/radar_service.dart
- lib/services/young_audience_filter_service.dart
+9
View File
@@ -0,0 +1,9 @@
# Patch 18.7.13 — Logo de chaîne dans le lecteur
- Affiche le vrai avatar/logo de la chaîne à gauche du nom sur la page vidéo.
- Utilise `ChannelDetails.thumbnailUrl`, déjà récupéré par les métadonnées inline.
- Fallback automatique vers l'icône générique si l'URL est vide ou si le chargement échoue.
- Aucun changement du pipeline de lecture Windows ou Android.
Fichier modifié :
- `lib/screens/video_player_page.dart`
+37
View File
@@ -0,0 +1,37 @@
# YouTube Personal 18.7.14 — Android gestes + veille
## Parité Android
Les fonctions Flutter partagées restent communes Windows / Android :
- dates et métadonnées
- page Bibliothèque / navigation
- filtre contenu très jeune
- langue préférée
- résolution par défaut
- avatar réel de chaîne dans le lecteur
## Gestes Android
Sur la surface vidéo :
- glissement vertical côté droit : volume système média
- glissement vertical côté gauche : luminosité de la fenêtre
- pincement à deux doigts : zoom 1.0x à 3.0x
- feedback central : Volume / Luminosité / Zoom
- double tap -10/+10 s existant conservé en plein écran
## Mise en veille Android
Le wakelock automatique de `media_kit_video` est désactivé uniquement sur Android et remplacé par une gestion contrôlée :
- lecture : écran maintenu allumé
- vidéo terminée : veille système immédiatement autorisée
- pause : veille autorisée après délai configurable
- valeurs : 2, 5, 10 minutes ou Jamais
- défaut : 5 minutes
- sortie de la page : maintien écran retiré et luminosité rendue au système
## Fichiers modifiés
- lib/screens/video_player_page.dart
- lib/screens/settings_page.dart
- lib/services/app_settings_service.dart
- android/app/src/main/java/com/studioweb/youtube_personal/MainActivity.java
## Protection Windows
Aucun fichier Windows HD n'est fourni dans ce patch.
`windows_hd_service.dart` et le pipeline 18.7.5 ne sont pas modifiés.
@@ -0,0 +1,30 @@
# YouTube Personal 18.7.15
## Cache Windows + Android et correctif compilation Android
### Correctif compilation Android
`Colors.black72` n'existe pas dans Flutter.
Le fond de l'indicateur des gestes utilise maintenant `Color(0xB8000000)`.
### Windows
- cache média persistant limité à 5 Go par défaut
- nettoyage LRU automatique des vidéos les moins récemment utilisées
- nettoyage lancé au démarrage de l'application
- emplacement du cache configurable
- détection et suppression séparée de l'ancien cache `%LOCALAPPDATA%\YouTubePersonal\media_cache`
- fallback au chemin par défaut si un disque personnalisé est indisponible
- aucun changement dans `windows_hd_service.dart`
### Android
- cache démuxeur media_kit limité en mémoire, 64 Mo par défaut
- choix 32 / 64 / 128 Mo
- cache disque vidéo désactivé par défaut
- option de cache disque temporaire dans le stockage interne privé de l'app
- option de cache disque temporaire dans le stockage externe privé de l'app
- affichage du chemin et de la taille du cache temporaire
- bouton pour vider le cache
- les restes d'une session interrompue sont nettoyés avant réutilisation
- les paramètres de cache s'appliquent aux nouvelles pages vidéo
### Sécurité de la lecture
Le cache est une optimisation. Une erreur de configuration du cache Android ne doit pas empêcher la lecture.
Le pipeline Windows protégé n'est pas réécrit.
@@ -0,0 +1,27 @@
# YouTube Personal 18.7.16 — Android gestures + pause sleep fix
## Correction des gestes Android
La détection `GestureDetector.onScale...` est remplacée par un `Listener`
de pointeurs bruts sur Android.
- moitié droite, glissement vertical : volume système média
- moitié gauche, glissement vertical : luminosité de la fenêtre
- deux doigts : zoom 1.0x à 3.0x
- seuil avant activation pour ne pas confondre un tap avec un glissement
- double tap existant conservé
- indicateur visuel Volume / Luminosité / Zoom conservé
Le Listener brut continue à recevoir les PointerEvents même lorsque les
contrôles media_kit participent au GestureArena.
## Correction de la veille après pause
Ajout d'un watchdog Android toutes les secondes :
- lecture / chargement : maintien écran actif
- pause : mémorisation du début réel de la pause
- après le délai choisi : FLAG_KEEP_SCREEN_ON retiré
- fin de vidéo : retrait immédiat, comportement déjà fonctionnel conservé
- "Jamais" : maintien de l'écran
## Windows
Aucun service Windows n'est modifié. Les nouvelles branches sont protégées
par `Platform.isAndroid`.
@@ -0,0 +1,26 @@
# YouTube Personal 18.7.17 — Android controls + gestures
## Régression corrigée
La 18.7.16 plaçait un `Listener` opaque AU-DESSUS de `Video`.
Les swipes volume/luminosité fonctionnaient, mais les taps n'atteignaient
plus correctement les contrôles `media_kit`.
## Nouvelle architecture Android
Le `Listener` brut est maintenant un PARENT du widget `Video`.
Conséquences :
- swipe vertical droite : volume conservé
- swipe vertical gauche : luminosité conservée
- pinch : zoom conservé
- contrôles media_kit reçoivent à nouveau les taps
- play/pause restauré
- barre/seek restaurée
- volume du lecteur restauré
- plein écran restauré
La couche `GestureDetector` qui recouvrait la vidéo n'est plus utilisée sur
Android.
## Windows
Aucun service Windows n'est modifié.
Le changement est protégé par `Platform.isAndroid`.
@@ -0,0 +1,29 @@
# YouTube Personal 18.7.18 — Gestes Android en plein écran
## Cause
En 18.7.17, les gestes Android étaient attachés autour du widget `Video`
de la page normale. Or media_kit crée une nouvelle route `Video` lorsqu'il
entre en plein écran. Cette nouvelle surface n'héritait donc pas du Listener.
## Correction
Les gestes sont maintenant attachés au `controls` builder Android :
- media_kit recopie ce builder dans sa route fullscreen
- les mêmes PointerEvents sont donc observés en mode normal et plein écran
- les contrôles MaterialVideoControls restent enfants du Listener et continuent
à recevoir les taps normalement
## Gestes
- moitié droite, swipe vertical : volume système média
- moitié gauche, swipe vertical : luminosité de la fenêtre
- pinch à deux doigts : zoom 1.0x à 3.0x
## Zoom
Le zoom passe maintenant par la propriété native mpv `video-zoom`, donc il
s'applique à la sortie vidéo elle-même et reste valable en plein écran.
## Feedback
Les indicateurs `Volume`, `Luminosité` et `Zoom` sont intégrés au builder de
contrôles et apparaissent donc aussi en plein écran.
## Windows
Aucun service Windows n'est modifié. Le changement est limité au chemin Android.

Some files were not shown because too many files have changed in this diff Show More