Light theme default + yt-dlp search fallback
This commit is contained in:
+45
@@ -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
|
||||||
@@ -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'
|
||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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 ""
|
||||||
@@ -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"
|
||||||
@@ -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 ""
|
||||||
@@ -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.
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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]`
|
||||||
@@ -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.
|
||||||
@@ -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 ...
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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é.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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 ...
|
||||||
|
```
|
||||||
@@ -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é.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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`.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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).
|
||||||
@@ -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%
|
||||||
|
```
|
||||||
@@ -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 ...
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
@@ -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é.
|
||||||
@@ -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 ...
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
@@ -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`.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
@@ -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`.
|
||||||
@@ -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`.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
@@ -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é.
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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`
|
||||||
@@ -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
Reference in New Issue
Block a user