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