From 5f764ab8f5e28ac2744f8cbff648087bcf70b767 Mon Sep 17 00:00:00 2001 From: alexandre patelli Date: Sat, 10 Mar 2018 18:25:20 +0100 Subject: [PATCH 01/69] Media Button Play/Pause, Previous and Next in Background Player --- app/src/main/AndroidManifest.xml | 6 +++ .../newpipe/player/BackgroundPlayer.java | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1be8c1f2c..18b3222a0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,6 +43,12 @@ android:launchMode="singleTask" android:label="@string/title_activity_background_player"/> + + + + + + Date: Sun, 11 Mar 2018 15:09:11 +0100 Subject: [PATCH 02/69] Direct use of AudioManager from AudioReactor --- .../java/org/schabi/newpipe/player/BackgroundPlayer.java | 7 ++----- .../org/schabi/newpipe/player/helper/AudioReactor.java | 9 +++++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 48249d876..bc28116cd 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -28,7 +28,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; -import android.media.AudioManager; import android.os.Build; import android.os.IBinder; import android.support.annotation.IntRange; @@ -82,7 +81,6 @@ public final class BackgroundPlayer extends Service { private BasePlayerImpl basePlayerImpl; private LockManager lockManager; - private AudioManager mAudioManager; private ComponentName mReceiverComponent; /*////////////////////////////////////////////////////////////////////////// @@ -122,9 +120,8 @@ public final class BackgroundPlayer extends Service { mBinder = new PlayerServiceBinder(basePlayerImpl); shouldUpdateOnProgress = true; - mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); mReceiverComponent = new ComponentName(this, MediaButtonReceiver.class); - mAudioManager.registerMediaButtonEventReceiver(mReceiverComponent); + basePlayerImpl.audioReactor.registerMediaButtonEventReceiver(mReceiverComponent); } @Override @@ -163,7 +160,7 @@ public final class BackgroundPlayer extends Service { basePlayerImpl = null; lockManager = null; - mAudioManager.unregisterMediaButtonEventReceiver(mReceiverComponent); + basePlayerImpl.audioReactor.unregisterMediaButtonEventReceiver(mReceiverComponent); stopForeground(true); stopSelf(); diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index 2c85cfc34..0e6642eed 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.player.helper; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.media.AudioFocusRequest; @@ -86,6 +87,14 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; } + public void registerMediaButtonEventReceiver(ComponentName componentName) { + audioManager.registerMediaButtonEventReceiver(componentName); + } + + public void unregisterMediaButtonEventReceiver(ComponentName componentName) { + audioManager.unregisterMediaButtonEventReceiver(componentName); + } + /*////////////////////////////////////////////////////////////////////////// // AudioFocus //////////////////////////////////////////////////////////////////////////*/ From 36457400e708dc284e2bbb219b472f8baf795aee Mon Sep 17 00:00:00 2001 From: alexandre patelli Date: Sun, 11 Mar 2018 19:23:00 +0100 Subject: [PATCH 03/69] Review Fixes --- .../java/org/schabi/newpipe/player/BackgroundPlayer.java | 7 +++++-- .../org/schabi/newpipe/player/helper/AudioReactor.java | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index bc28116cd..3e9a63886 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -152,6 +152,7 @@ public final class BackgroundPlayer extends Service { lockManager.releaseWifiAndCpu(); } if (basePlayerImpl != null) { + basePlayerImpl.audioReactor.unregisterMediaButtonEventReceiver(mReceiverComponent); basePlayerImpl.stopActivityBinding(); basePlayerImpl.destroy(); } @@ -160,8 +161,6 @@ public final class BackgroundPlayer extends Service { basePlayerImpl = null; lockManager = null; - basePlayerImpl.audioReactor.unregisterMediaButtonEventReceiver(mReceiverComponent); - stopForeground(true); stopSelf(); } @@ -594,6 +593,10 @@ public final class BackgroundPlayer extends Service { pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT); } else if (keycode == KeyEvent.KEYCODE_HEADSETHOOK) { pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT); + } else if (keycode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { + pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT); + } else if (keycode == KeyEvent.KEYCODE_MEDIA_REWIND) { + pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT); } if (pendingIntent != null) { try { diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index 0e6642eed..df30c3e79 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -88,10 +88,18 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au } public void registerMediaButtonEventReceiver(ComponentName componentName) { + if (android.os.Build.VERSION.SDK_INT > 27) { + Log.e(TAG, "registerMediaButtonEventReceiver has been deprecated and maybe not supported anymore."); + return; + } audioManager.registerMediaButtonEventReceiver(componentName); } public void unregisterMediaButtonEventReceiver(ComponentName componentName) { + if (android.os.Build.VERSION.SDK_INT > 27) { + Log.e(TAG, "unregisterMediaButtonEventReceiver has been deprecated and maybe not supported anymore."); + return; + } audioManager.unregisterMediaButtonEventReceiver(componentName); } From e7a0b850df2332e83dcd0551bc66fa19e4390134 Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Mon, 12 Mar 2018 14:50:29 +0000 Subject: [PATCH 04/69] Translated using Weblate (Korean) Currently translated at 100.0% (324 of 324 strings) --- app/src/main/res/values-ko/strings.xml | 105 +++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 4a0caee97..cd273af38 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -284,4 +284,109 @@ 여기서부터 재생 여기서부터 백그라운드에서 재생 여기서부터 팝업에 재생 +스트리밍 플레이어를 찾을 수 없습니다. VLC를 설치하면 플레이하실 수 있습니다 + 스트리밍 파일 다운로드하기. + 정보 보기 + + 북마크 + + 이곳에 추가 + + 정확하지는 않지만 빠른 탐색 + 정확하지 않은 탐색은 빠르게 위치로 탐색할 수 있지만 정확도는 떨어집니다 + 다음 스트림을 자동으로 재생열에 추가하기 + 전 스트림이 무한 반복 재생 큐가 아닐 때 관련된 스트림 자동 재생하기. + 기본 콘텐츠 국가 + 서비스 + 디버그 + 라이브 (LIVE) + 항상 + 한번만 + + 디바이스 방향 토글 + 백그라운드로 전환 + 팝업으로 전환 + 기본으로 전환 + + 데이터베이스 가져오기 + 데이터베이스 내보내기 + 현재 시청 기록 및 구독 목록을 덮어쓰기 됩니다 + 시청 기록, 구독 목록, 재생목록 내보내기. + 외부 플레이어는 이러한 종류의 링크를 지원하지 않습니다 + 잘못된 URL + 발견된 비디오 스트림 없음 + 발견된 오디오 스트림 없음 + + 드래그하여 재배열 + + 만들기 + 1개 삭제하기 + 모두 삭제하기 + 취소 + 이름 바꾸기 + + 로봇인지 확인합니다 + 이 항목을 시청 기록에서 삭제하시겠습니까? + 모든 항목을 시청 기록에서 삭제하시겠습니까? + 마지막으로 재생 + 가장 많이 재생 + + 내보내기 완료 + 가져오기 완료 + 유효한 ZIP 파일 없음 + 경고: 모든 파일 가져오기를 실패했습니다. + 이것은 현재 설정을 덮어쓸 것입니다. + + 드로어 열기 + 드로어 닫기 + 여기에 무언가가 추가될 거에요~ :D + + + 선호하는 플레이어로 열기 + 선호하는 플레이어 + + 비디오 플레이어 + 백그라운드 플레이어 + 팝업 플레이어 + 항상 묻기 + + 정보 가져오는 중… + 요청한 콘텐츠를 로딩 중입니다 + + 새로운 재생목록 만들기 + 재생목록 삭제 + 재생목록 이름 바꾸기 + 이름 + 재생목록에 추가 + 재생목록 썸네일로 설정 + + 재생목록 북마크하기 + 북마크 제거하기 + + 이 재생목록을 삭제하시겠습니까? + 재생목록 생성 성공 + 재생목록에 추가됨 + 재생목록 썸내일이 바뀜 + 재생목록 삭제 실패 + + 자막 없음 + + 꼭 맞게 하기 + 채우기 + 확대 + + 자동 생성됨 + 자막 폰트 크기 + 작은 폰트 + 보통 폰트 + 큰 폰트 + + 동기화 + + LeakCanary 할성화 + 메모리 누수 모니터링은 힙 덤핑시 앱이 불안정할 수 있습니다 + + Out-of-Lifecycle 에러 보고 + 프래그먼트 또는 버려진 액티비티 주기 밖에서 일어나는 전달할 수 없는 Rx 예외를 강제적으로 보고하기 + From a9fea9f6065550890268692d845fed5e62cc7443 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Mon, 12 Mar 2018 15:57:35 +0100 Subject: [PATCH 05/69] fix release crash because of setting sidebar header --- app/src/main/java/org/schabi/newpipe/MainActivity.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java index 573479ea7..1c62690c2 100644 --- a/app/src/main/java/org/schabi/newpipe/MainActivity.java +++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java @@ -176,9 +176,11 @@ public class MainActivity extends AppCompatActivity { // when the user returns to MainActivity drawer.closeDrawer(Gravity.START, false); try { - String selectedServiceName = NewPipe.getService( - ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName(); - headerServiceView.setText(selectedServiceName); + if(BuildConfig.BUILD_TYPE != "release" ) { + String selectedServiceName = NewPipe.getService( + ServiceHelper.getSelectedServiceId(this)).getServiceInfo().getName(); + headerServiceView.setText(selectedServiceName); + } } catch (Exception e) { ErrorActivity.reportUiError(this, e); } From 6049a1f2f59ee623628e6eb3a8e0bcc2b49045eb Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Mon, 12 Mar 2018 16:18:03 +0100 Subject: [PATCH 06/69] fix playlist banner foo --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 814006051..88fe38183 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.TeamNewPipe:NewPipeExtractor:b1130629bb' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:fce324d1bc74bc' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:1.10.19' From bdf044d264043539d55dab8647b4e161a8e934bf Mon Sep 17 00:00:00 2001 From: Heimen Stoffels Date: Mon, 12 Mar 2018 16:00:07 +0000 Subject: [PATCH 07/69] Translated using Weblate (Dutch) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-nl/strings.xml | 42 +++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4f8e5b0e5..07fa4d720 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -406,4 +406,44 @@ te openen in pop-upmodus Automatisch een gerealteerde stream toekennen als het afspelen van de laatste stream strat in een niet-herhalende afspeelwachtlijst. SYNCHRONISEREN - + Bestand + + Ongeldige map + Ongeldig bestand/Ongeldige inhoudsbron + Het bestand bestaat niet of u beschikt niet over voldoende machtiging om het te lezen/er naar te schrijven + De bestandsnaam mag niet leeg zijn + Er is een fout opgetreden: %1$s + + Importeren/Exporteren + Importeren + Importeren uit + Exporteren naar + + Bezig met importeren… + Bezig met exporteren… + + Bestand importeren + Vorige exportering + + Importeren van abonnementen is mislukt + Exporteren van abonnementen is mislukt + + Als u uw YouTube-abonnementen wilt importeren, dan heeft u het exportbestand nodig. Dit kan worden gedownload door het volgen van onderstaande stappen: +\n +\n1. Ga naar dit adres: %1$s +\n2. Log, indien nodig, in op uw account +\n3. De download met het exportbestand zou nu moeten starten + Als u uw SoundCloud-abonnementen wilt importeren, dan moet u uw profiel-URL of ID kennen. Als u hem kent, typ hem dan hieronder in. +\n +\nAls u hem niet kent, volg dan onderstaande stappen: +\n +\n1. Kies een webbrowser en schakel bureaubladmodus in (de website is niet beschikbaar voor mobiele apparaten) +\n2. Volg deze link: %1$s +\n3. Log, indien nodig, in op uw account +\n4. Kopieer de link van de pagina waar u op terechtkomt (dat is uw profiel-URL) + uwid, soundcloud.com/uwid + + Let op: deze actie kan veel MB\'s van uw netwerk gebruiken. +\n +\nWilt u doorgaan? + From 4aa2c1c2c21c088c644c1706e72385cb066d6cf2 Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Mon, 12 Mar 2018 15:20:18 +0000 Subject: [PATCH 08/69] Translated using Weblate (Korean) Currently translated at 95.0% (326 of 343 strings) --- app/src/main/res/values-ko/strings.xml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index cd273af38..33b730e3e 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -78,7 +78,7 @@ 다운로드 메뉴를 설정할 수 없습니다 실시간 스트리밍 비디오는 아직 지원되지 않습니다. 어떠한 스트림도 가져올 수 없습니다 - 죄송합니다 + 죄송합니다. 오류가 발생했습니다. 이메일을 통해 오류 보고 죄송합니다. 오류가 발생했습니다. 보고 @@ -389,4 +389,8 @@ Out-of-Lifecycle 에러 보고 프래그먼트 또는 버려진 액티비티 주기 밖에서 일어나는 전달할 수 없는 Rx 예외를 강제적으로 보고하기 - +파일 + + 잘못된 디렉토리 + 잘못된 파일/콘덴츠 소스 + From 158f0aa2d971463d705312da1ffc9d75610d9081 Mon Sep 17 00:00:00 2001 From: Emanuele Petriglia Date: Mon, 12 Mar 2018 16:53:28 +0000 Subject: [PATCH 09/69] Translated using Weblate (Italian) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-it/strings.xml | 42 +++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 9b294a9a7..8ad96dd3f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -411,4 +411,44 @@ Aggiungi automaticamente un flusso correlato mentre il playback parte dall\'ultimo flusso in una cosa non ripetitiva. SINCRONIZZAZIONE - + File + + Cartella invalida + Fonte del contenuto o file invalido + Il file non esiste o non si hanno i permessi sufficenti per leggerlo o scriverci + Il nome del file non può essere vuoto + Si è verificato un errore: %1$s + + Importa/Esporta + Importa + Importa da + Esporta a + + Importando… + Esportando… + + Importa file + Esportazione precedente + + L\'importazione delle iscrizioni è fallita + L\'esportazione delle iscrizioni è fallita + + Per importare le tue iscrizioni YouTube devi procurarti il file d\'esportazione, il quale può essere scaricato seguendo le seguenti istruzioni: +\n +\n1. Vai a questo URL: %2$s +\n2. Accedi al tuo account quando è richiedto +\n3. Un download dovrebbe essere partito (è il file d\'esportazione) + Per importare i tuoi seguiti di SoundCloud devi conoscere l\'URL del tuo profilo od il tuo ID. Se lo sai, ti basta scrivere uno dei due nell\'immisione in basso ed hai fatto. +\n +\nSe non lo sai, puoi seguire le seguenti istruzioni: +\n +\n1. Abilita la \"modalità desktop\" nel browser che usi (il sito non funziona nella modalità mobile) +\n2. Vai a questo URL: %2$s +\n3. Accedi al tuo account quando richiesto +\n4. Copia l\'URL a cui vieni indirizzato (è l\'URL del tuo profilo) + iltuoid, soundcloud.com/iltuoid + + Tieni in mente che questa operazione può richiedere un costo di connessione dati. +\n +\nVuoi continuare? + From 21e300b9f36efd37d13d9ae3ab850a77902c13ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Freddy=20Mor=C3=A1n=20Jr?= Date: Mon, 12 Mar 2018 20:18:00 +0000 Subject: [PATCH 10/69] Translated using Weblate (Spanish) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-es/strings.xml | 40 ++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5499188a3..d00a6be85 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -410,4 +410,44 @@ abrir en modo popup Automáticamente añadir un vídeo relacionado cuando el reproductor llegue al último vídeo en una lista de reproducción no repetible. DIRECTO SINCRONIZAR +Archivo + + Directorio invalido + Fuente del archivo/contenido inválida + El archivo no existe o el permiso es insuficiente para leerlo o escribir en él + El nombre del archivo no puede estar vacío + Ocurrió un error: %1$s + + Importar/Exportar + Importar + Importar desde + Exportar a + + Importando… + Exportando… + + Importar archivo + Exportación anterior + + Importación de suscripciones fallida + Exportación de suscripciones fallida + + Para importar sus suscripciones de YouTube, necesitará el archivo de exportación, el cual puede ser descargado siguiendo estas instrucciones: +\n +\n1. Vaya a esta URL: %1$s +\n2. Ingrese a su cuenta cuando se le pida +\n3. Una descarga debería comenzar (ese es el archivo de exportación) + Para importar sus seguimientos de SoundCloud, debe conocer la URL o el ID de su perfil. Si es así, simplemente escriba cualquiera de ellos en la entrada de abajo y ya está listo para comenzar. +\n +\nSi no es así, puede seguir estos pasos: +\n +\n1. Active el \"modo escritorio\" en algún navegador (el sitio no está disponible para dispositivos móviles) +\n2. Vaya a esta URL: %1$s +\n3. Ingrese a su cuenta cuando se le pida +\n4. Copie la URL a la que fue redireccionado (esa es la URL de su perfil) + suID, soundcloud.com/suID + + Tenga en cuenta que esta operación puede ser costosa para la red. +\n +\n¿Desea continuar? From 00dee43a1e90ae0bd8246f187fa9a37697f0fb09 Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Tue, 13 Mar 2018 11:23:54 +0000 Subject: [PATCH 11/69] Translated using Weblate (Korean) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-ko/strings.xml | 39 ++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 33b730e3e..60ee4e523 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -241,7 +241,7 @@ 번역, 디자인, 코딩 등 다양한 기여를 언제나 환영합니다. 향상에 참여해주세요! GitHub에서 보기 기부 - 뉴파이프는 자원봉사자들이 자발적으로 여가 시간을 활용해 개발하고 있습니다. 이제 이러한 노력에 보답할 시간입니다. + 뉴파이프는 자원봉사자들이 자발적으로 여가 시간을 활용해 개발하고 있습니다. 이제 이러한 노력에 보답할 시간입니다! 보답하기 웹사이트 뉴파이프에 관한 최신 및 상세 정보를 얻으려면 웹사이트를 방문하세요. @@ -393,4 +393,39 @@ 잘못된 디렉토리 잘못된 파일/콘덴츠 소스 - + 파일이 존재하지 않거나 읽기/쓰기 권환이 없습니다 + 파일 이름이 비어 있으면 안됩니다 + 오류 발생: %1$s + + 가져오기/내보내기 + 가져오기 + 이곳으로부터 가져오기 + 이곳으로 내보내기 + + 가져오는 중.… + 내보내는 중… + + 파일 가져오기 + 이전 내보내기 + + 구독 목록 가져오기 실패 + 구독 목록 내보내기 실패 + + YouTube 구독 목록을 가져오려면 내보내기 파일이 필요합니다. 다운로드 하려면 +\n1. 이곳으로 가세요: $1$s +\n2. 로그인이 필요하면 하세요 +\n3. 다운로드가 곧 시작 됩니다 (이 파일이 내보내기 파일 입니다) + SoundCloud 팔로잉 목록을 가져오려면 당신의 프로필 URL 및 ID를 알아야 합니다. 알고 있다면 아래에 있는 빈칸에 입력해 주세요. +\n +\n만약 모르신다면, 다음을 참고하세요: +\n +\n1. 모바일 환경이시면 브라우저 설정에서 데스크탑 모드를 활성화해주세요. Chrome 모바일에서는 오른쪽 ... 클릭시 아래쪽에 있습니다. +\n2. 이 주소로 가세요: %1$s +\n3. 로그인이 필요하면 하세요. +\n4. 리디렉트된 곳의 URL을 복사하세요. (이 URL이 당신의 프로필 URL 입니다) + 프로필ID, soundcloud.com/프로필ID + + 경고: 데이터 소모량이 늘어날 수 있습니다. +\n +\n진행하시겠습니까? + From b08728b64511ed3b3d7baa46389cbead3dd3a87a Mon Sep 17 00:00:00 2001 From: Eduardo Caron Date: Mon, 12 Mar 2018 17:30:41 +0000 Subject: [PATCH 12/69] Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.8% (339 of 343 strings) --- app/src/main/res/values-pt-rBR/strings.xml | 42 +++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 96e6ccb70..ea22efa95 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -385,4 +385,44 @@ abrir em modo popup Anexar automaticamente uma stream relacionada quando a reprodução iniciar na última stream em uma fila não repetitiva Sincronizar - + Arquivo + + Diretório inválido + Origem do arquivo/conteúdo inválido + Arquivo não existe ou não há permissão para ler ou escrever nele + Nome do arquivo não pode ser vazio + Um erro ocorreu: %1$s + + Importar/Exportar + Importar + Importar de + Exportar para + + Importando… + Exportando… + + Importar arquivo + Exportação anteriore + + Importação de inscrições falhou + Exportação de inscrições falhou + + "Para importar inscrições do YouTube você vai precisar exportar o arquivo, o que pode ser baixado seguindo estas informações: +\n +\n1. Vá para este link: %1$s +\n2. Faça login na sua conta quando solicitado +\n3. O download deverá começar (isto é exportar arquivo)" + Para importar as contas que você segue no SoundCloud, você terá que saber o link ou id do seu perfil. Se você souber, basta escrever um deles no campo abaixo e estará tudo pronto. +\n +\nSe você não souber, você pode seguir estas etapas: +\n +\n1. Habilite \"modo desktop\" em algum navegador da internet ( o site não está disponível para dispositivos móveis) +\n2. Vá para esta url: %1$s +\n3. Faça login na sua conta quando solicitado +\n4. Copie o link no qual que você foi redirecionado (este é o link do seu perfil) + seuid, soundcloud.com/seuid + + Tenha em mente que esta operação poderá usar bastante a conexão com a internet. +\n +\nVocê deseja continuar? + From 24f2999669a8db7a92a69f1325836d160502ed90 Mon Sep 17 00:00:00 2001 From: alexandre patelli Date: Tue, 13 Mar 2018 22:57:59 +0100 Subject: [PATCH 13/69] Handling play/pause button from different headsets --- .../main/java/org/schabi/newpipe/player/BackgroundPlayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 3e9a63886..06b62f46f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -591,7 +591,7 @@ public final class BackgroundPlayer extends Service { pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT); } else if (keycode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) { pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT); - } else if (keycode == KeyEvent.KEYCODE_HEADSETHOOK) { + } else if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PAUSE || keycode == KeyEvent.KEYCODE_MEDIA_PLAY) { pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT); } else if (keycode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT); From 7d047e612e2ff43bbead9c073ce1d9e4ae76b512 Mon Sep 17 00:00:00 2001 From: cozyplanes Date: Tue, 13 Mar 2018 11:24:50 +0000 Subject: [PATCH 14/69] Translated using Weblate (Korean) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-ko/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 60ee4e523..eaa97a2a5 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -215,7 +215,7 @@ 이 권한은 팝업 모드에서 \n열기 위해 필요합니다 - reCAPTCHA + 로봇인지 확인 (reCAPTCHA) reCAPTCHA Challenge 요청됨 다운로드 From 1ac7b2b8cbd44c0ffa5546a2522fee9f368e3d72 Mon Sep 17 00:00:00 2001 From: ezjerry liao Date: Tue, 13 Mar 2018 12:30:13 +0000 Subject: [PATCH 15/69] Translated using Weblate (Chinese (Traditional)) Currently translated at 98.2% (337 of 343 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a112b4830..f506d4949 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -387,4 +387,23 @@ 在非重複播放佇列中的最後一個串流上開始播放時,自動附上相關串流。 同步 + 檔案 + + 無效的目錄 + 無效的檔案/內容來源 + 檔案名稱不能留空 + 發生錯誤:%1$s + + 匯入/匯出 + 匯入 + 匯入來自 + 匯出到 + + 正在匯入… + 正在匯出… + + 匯入檔案 + 訂閱匯入失敗 + 訂閱匯出失敗 + From 2dbfc28d696a8f82d284fa5f7685c30c8b19a6ca Mon Sep 17 00:00:00 2001 From: Edwar Tikhonov Date: Wed, 14 Mar 2018 15:34:30 +0000 Subject: [PATCH 16/69] Translated using Weblate (Russian) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-ru/strings.xml | 154 ++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 18 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 58fb004c7..44ab1f984 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -58,7 +58,7 @@ Нажмите поиск, чтобы начать Подождите… Файл уже существует - Потоки + Темы OK Начать Пауза @@ -69,7 +69,7 @@ Ошибка Сервер не поддерживается NewPipe скачивает - Неправильный URL или Интернет не доступен + Неправильный URL или нет доступа к интернету Нажмите для деталей Скопировано в буфер обмена Выберите доступную папку для загрузки @@ -137,19 +137,19 @@ Обновить Очистить Использовать старый плеер - Жесты + Контроль жестов Всё Фильтр - Новая миссия + Новая цель Что:\\nЗапрос:\\nЯзык контента:\\nСервис:\\nВремя по Гринвичу:\\nПакет:\\nВерсия:\\nВерсия ОС:\\nГлобальный диапазон IP: Это разрешение нужно для -\nвоспроизведения видео в отдельном окне +\nвоспроизведения в окне reCAPTCHA Открыть в отдельном окне - Показывать подсказки во время поиска + Показывать подсказки в поиске Позже Отключено @@ -162,7 +162,7 @@  тыс. Разрешение в режиме всплывающего окна Запоминать последний размер и положение всплывающего окна - Живой поиск + Поисковые подсказки Лучшее разрешение Старый встроенный плеер на Mediaframework @@ -265,7 +265,7 @@ Выберите киоск Киоск - В тренде + Тренды Топ 50 Новое и горячее Добавлено в очередь в фоне @@ -275,22 +275,22 @@ Не удалось воспроизвести этот поток Подробности Настройки аудио - Пока нет подписок + Пока нет подписок на каналы Удалить Отписаться Подписка отменена - Подсказка о длинном нажатии - Отображать подсказку о длинном нажатии на кнопки \"В фоне\" и \"В окне\" для добавления в очередь + Показывать напоминание о длинном нажатии + Показывать подсказку при нажатии на иконку «В окне» или «В фоне» на странице сведений о видео [Неизвестно] Восстановление после ошибки проигрывателя - Воспроизведение в фоне - Воспроизведение в окне - Зажмите чтобы добавить в очередь - Добавить в очередь в фоне - Добавить в очередь в окне - Воспроизвести + В фоне + В окне + Зажмите, чтобы добавить в очередь + Добавить в очередь «В фоне» + Добавить в очередь «В окне» + Воспроизвести тут Воспроизвести в фоне Воспроизвести в окне Ни одного потокового проигрывателя не было найдено (вы можете установить VLC) @@ -326,5 +326,123 @@ Всегда спрашивать Получение информации… - Загрузка запрашиваемого контента + Загрузка запрошенного контента +Загрузка файла прямой трансляции. + Показать информацию + + Закладки + + Добавить к + + Использовать быстрый, но неточный поиск + Неточный поиск позволяет плееру искать позицию быстрее, но с пониженной точностью + Автоматическая очередь следующего стрима + Автоматически добавлять связанные потоки, когда воспроизведение начинается с последнего потока в неповторяющейся очереди воспроизведения. + Отладка + Файл + + Импорт данных + Экспорт данных + Ваша текущая история и подписки будут перезаписаны + Экспорт истории, подписок и плейлистов. + Неправильная директория + Неправильный файл/контент источника + Файл не существует или нет разрешения на его прочтение или запись + Имя файла не может быть пустым + Произошла ошибка: %1$s + + Перетащите, чтобы изменить порядок + + Создать + Удалить одно + Удалить всё + Отклонить + Переименовать + + Вы хотите удалить этот элемент из истории поиска? + Вы уверены, что хотите удалить все элементы из истории? + Последнее проигрывание + Наиболее проигрываемые + + Экспорт завершён + Импорт завершён + Нет верного Zip файла + Предупреждение: нет возможности импорта всех файлов. + Это перезапишет вашу текущую установку. + + Что-то будет тут, скоро ;D + + + Всегда спрашивать + + Создать новый плейлист + Удалить плейлист + Переименовать плейлист + Имя + Добавить в плейлист + Установить как иконку плейлиста + + Пометить плейлист + Удалить пометку + + Вы хотите удалить этот плейлист? + Плейлист успешно создан + Добавлено в плейлист + Иконка плейлиста изменена + Ошибка при удалении плейлиста + + Без подписи + + Уместить + Заполнить + Приближение + + Автоматически созданный + Размер шрифта подписи + Маленький шрифт + Обычный шрифт + Большой шрифт + + Синхронизировать + + Включить LeakCanary + Мониторинг утечки памяти может привести к зависанию приложения + + Ошибки отчёта вне очереди + Форсировать отчетность о недопустимых исключениях Rx, возникающих за пределами фрагмента или цикла деятельности, после размещения + + Импорт/Экспорт + Импорт + Импорт из + Экспорт в + + Импорт… + Экспорт… + + Импорт файла + Предыдущий экспорт + + Импорт подписок провален + Экспорт подписок провален + + Для импорта подписок из YouTube вам необходимо файл экспорта, которые можно загрузить в соответствии с этими инструкциями: +\n +\n1. Перейдите на: %1$s +\n2. Войдите в ваш аккаунт, если необходимо +\n3. Загрузка должна начаться (это файл экспорта) + "Для импорта ваших подписок из SoundCloud вы должны знать ссылку на ваш профиль или id. Если вы знаете, просто напишите это в поле ниже и будьте готовы начинать. +\n +\nЕсли вы не знаете, то проследуйте следующей инструкции: +\n +\n1. Включите \"режим рабочего стола\" в браузере (сайт недоступен на телефоне) +\n2. Пройдите на: %1$s +\n3. Войдите в аккаунт, если надо +\n4. Скопируйте адрес из адресной строки (это адрес вашего профиля) +\n +\n" + вашid, soundcloud.com/вашid + + Помните, что за выход в интернет может взиматься плата. +\n +\nВы хотите продолжить? From e49c4162e562703a4c4f670050bab0daea98d88e Mon Sep 17 00:00:00 2001 From: ezjerry liao Date: Wed, 14 Mar 2018 13:37:29 +0000 Subject: [PATCH 17/69] Translated using Weblate (Chinese (Traditional)) Currently translated at 98.5% (338 of 343 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f506d4949..6f7ffcd26 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -406,4 +406,6 @@ 訂閱匯入失敗 訂閱匯出失敗 + 之前的匯出 + From d63c0a32ebffda5adf858a2a6fe4eba348d370c3 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Thu, 15 Mar 2018 14:19:45 +0000 Subject: [PATCH 18/69] Translated using Weblate (Hebrew) Currently translated at 75.8% (260 of 343 strings) --- app/src/main/res/values-he/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index cb69d6639..8cb0e4571 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -308,4 +308,8 @@ יצוא מסד נתונים נגנים חיצוניים לא תומכים בסוגי קישורים אלה כתובת שגויה + קובץ + + העברה לרקע + העברה לחלון צץ From 65726d75ccd4849ffd9dec9d81a6cb729d978177 Mon Sep 17 00:00:00 2001 From: Edwar Tikhonov Date: Wed, 14 Mar 2018 15:35:45 +0000 Subject: [PATCH 19/69] Translated using Weblate (Russian) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 44ab1f984..cd2737723 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -169,7 +169,7 @@ Запрос reCAPTCHA Запрошен ввод reCAPTCHA - Показывать более высокие разрешения + Показывать более высокое разрешение NewPipe в окне О NewPipe Настройки From 2d1bc6436a74433bcdf8305637dd2d00767e0b5c Mon Sep 17 00:00:00 2001 From: Olexandr Nesterenko Date: Wed, 14 Mar 2018 17:19:04 +0000 Subject: [PATCH 20/69] Translated using Weblate (Ukrainian) Currently translated at 98.8% (339 of 343 strings) --- app/src/main/res/values-uk/strings.xml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 0608d8150..fa06ebd87 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -392,4 +392,26 @@ Автоматично додавати пов\'язаний стрим, під час початку програвання останнього стриму. СИНХРОНІЗАЦІЯ + Файл + + Неправильна тека + Неправильний файл/контент джерела + Файл не існує, або немає дозволу на його запис чи читання + Ім\'я файлу не повинно бути порожнім + Трапилась помилка: %1$s + + Імпорт/Експорт + Імпорт + Імпорт з + Експорт до + + Імпортування… + Експортування… + + Імпорт файлу + Попередній експорт + + Не вдалось імпортувати підписки + Не вдалося експортувати підписки + From 37ff4e9aeb1e115af708fea3a0aea3cd4dff885c Mon Sep 17 00:00:00 2001 From: E T Date: Fri, 16 Mar 2018 11:20:59 +0000 Subject: [PATCH 21/69] Translated using Weblate (Turkish) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-tr/strings.xml | 42 +++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9fbb42e0b..a6e3c7a1b 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -401,4 +401,44 @@ Yinelemeyen oynatma kuyruğundaki son akış başladığında ilişkili akışı kuyruğun sonuna kendiliğinden ekle. EŞZAMANLA - + Dosya + + Geçersiz dizin + Geçersiz dosya/içerik kaynağı + Dosya yok ya da okuma veya yazma izni yetersiz + Dosya adı boş olamaz + Hata oluştu: %1$s + + İçe/Dışa Aktar + İçe Aktar + Şuradan içe aktar + Şuna dışa aktar + + İçe aktarılıyor… + Dışa aktarılıyor… + + Dosyayı içe aktar + Önceki dışa aktarım + + Aboneliklerin içe aktarımı başarısız + Aboneliklerin dışa aktarımı başarısız + + YouTube aboneliklerinizi içe aktarmak için dışa aktarılmış dosya gerekiyor, dosya şu yönergeler izlenerek indirilebilir: +\n +\n1. Şu adrese gidin: %1$s +\n2. Sorulduğunda hesabınıza giriş yapın +\n3. İndirme başlamalı (bu dışa aktarılmış dosyadır) + SoundCloud takiplerinizi içe aktarmak için profil adresinizi veya kimliğinizi bilmelisiniz. Eğer biliyorsanız, ikisinden birini aşağıdaki giriye yazın ve işte hazırsınız. +\n +\nEğer bilmiyorsanız şu adımları izleyebilirsiniz: +\n +\n1. Herhangi bir tarayıcıda \"masaüstü kipi\"ni açın (site, mobil aygıtlar için uygun değildir) +\n2. Şu adrese gidin: %1$s +\n3. Sorulduğunda hesabınıza giriş yapın +\n4. Yönlendirildiğiniz adresi kopyalayın (bu sizin profil adresinizdir) + kimliginiz, soundcloud.com/kimliginiz + + Bu sürecin ağ masrafına nedenn olabileceğini unutmayın. +\n +\nDevam etmek istiyor musunuz? + From 579efa15c74652c490686707cd5e1f709f889d99 Mon Sep 17 00:00:00 2001 From: E T Date: Fri, 16 Mar 2018 11:21:22 +0000 Subject: [PATCH 22/69] Translated using Weblate (Turkish) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-tr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a6e3c7a1b..736bb12ae 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -438,7 +438,7 @@ \n4. Yönlendirildiğiniz adresi kopyalayın (bu sizin profil adresinizdir) kimliginiz, soundcloud.com/kimliginiz - Bu sürecin ağ masrafına nedenn olabileceğini unutmayın. + Bu sürecin ağ masrafına neden olabileceğini unutmayın. \n \nDevam etmek istiyor musunuz? From c092fc8e18f00997889f08d596c4ceeabf6ae31b Mon Sep 17 00:00:00 2001 From: AB Date: Sat, 17 Mar 2018 12:40:26 +0000 Subject: [PATCH 23/69] Translated using Weblate (Ukrainian) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-uk/strings.xml | 35 ++++++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index fa06ebd87..ce1b835e5 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -384,7 +384,7 @@ Під час роботи LeakCanary застосунок може стати несприйнятливим під час гіп-дампінґу Зазвітувати Out-of-Lifecycle хиби - Примусове звітування про неможливість доставлення Rx винятків, яку відбуваються за межами фраґменту, або діяльності життєвого циклу після усунення + Примусове звітування про неможливість доставлення Rx винятків, які відбуваються за межами фраґменту або діяльності життєвого циклу після усунення Використовувати неточне шукання Неточне шукання дозволяє програвачеві рухатися позиціями швидше, проте з меншою точністю @@ -400,18 +400,37 @@ Ім\'я файлу не повинно бути порожнім Трапилась помилка: %1$s - Імпорт/Експорт - Імпорт - Імпорт з - Експорт до + Імпортування/Експортування + Імпортування + Імпортувати з + Експортувати до Імпортування… Експортування… - Імпорт файлу - Попередній експорт + Імпортування файлу + Попереднє експортування Не вдалось імпортувати підписки Не вдалося експортувати підписки - + Аби імпортувати ваші підписання Ютюб, вам буде потрібно експортувати файл, який можна буде завантажити наступним чином: +\n +\n1. Перейдіть за цією ланкою: %1$s +\n2. За запитом увійдіть до вашої обліківки +\n3. Завантаження має початися (експортований файл) + Для імпортування ваших підписок з SoundCloud, ви маєте знати url вашого профайлу або ID. Якщо ви знаєте їх, упишіть їх нижче та можна працювати. +\n +\nЯкщо ви не маєте їх, зробіть наступним чином: +\n +\n1. Увімкніть режимі \"desktop\" у будь-якому з переглядачів (сайт не має підтримки мобільних ґаджетів) +\n +\n2. Перейдіть за цією ланкою: %1$s +\n3. За запитом увійдіть до вашої обліківки +\n4. Скопіюйте url, до якого вас відішле (це й є url вашого профайлу) + yourid, soundcloud.com/yourid + + Майте на увазі: ця операція може потребувати багато трафіку. +\n +\nПродовжуватимете? + From 80593e774c059605478d4617de894f09cc3d5dc6 Mon Sep 17 00:00:00 2001 From: ezjerry liao Date: Sat, 17 Mar 2018 12:19:31 +0000 Subject: [PATCH 24/69] Translated using Weblate (Chinese (Traditional)) Currently translated at 99.7% (342 of 343 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6f7ffcd26..c32a6f272 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -408,4 +408,15 @@ 之前的匯出 - + 檔案不存在或沒有足夠的權限讀取或寫入 + 要匯入您的 YouTube 訂閱,您必須匯出檔案,可以按照以下說明進行下載: +\n +\n1. 轉到此網址:%1$s +\n2. 當被詢問時登入您的帳戶 +\n3. 下載應該開始 ( 這就是匯出的檔案 ) + yourid, soundcloud.com/yourid + + 請記住,此操作可能會造成網路昂貴花費。 +\n +\n您想繼續嗎? + From 1910e81ad945e29b3225b7d9869d008fb9dca668 Mon Sep 17 00:00:00 2001 From: AB Date: Sat, 17 Mar 2018 12:47:36 +0000 Subject: [PATCH 25/69] Translated using Weblate (Ukrainian) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-uk/strings.xml | 32 +++++++++++++------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index ce1b835e5..a032ec339 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -126,9 +126,9 @@ Будь ласка, оберіть теку для завантаження Потоковий програвач не знайдено (ви можете встановити VLC для відтворення) - Відкрити у виринальному режимі + Відкрити у віконному режимі Певні роздільності НЕ МАТИМУТЬ звуку якщо цей параметр увімкнено - NewPipe у виринальному вікні + NewPipe у віконному режимі Підписатися Ви підписалися Ви відписалися від каналу @@ -141,14 +141,14 @@ Новинки Тло - Виринальне вікно + У вікні - Типова роздільна здатність виринального вікна + Типова роздільна здатність вікна Не всі пристрої підтримують програвання 2K/4K відео Показувати більші роздільні здатності Типовий відео формат - Пам\'ятати розмір виринального вікна та положення - Пам\'ятати останній розмір та позицію виринального вікна + Пам\'ятати розмір та положення вікна + Пам\'ятати останній розмір та позицію вікна Керування жестами Використовувати жести для контролю яскравості та гучності програвача Шукати схожі @@ -165,10 +165,10 @@ Програвач Поведінка Історія - Виринальне вікно - Відворення у виринальному вікні + Вікно + Відворення у вікні Додано до фонового програвання - Додано до чергу у виринальному вікні + Додано до чергу у вікно Плейлист Фільтрувати Оновити @@ -181,12 +181,12 @@ Тільки тепер NewPipe сповіщення - Сповіщення для фонового та виринального програвача NewPipe + Сповіщення для фонового та віконного програвачів NewPipe [Невідомо] Перемкнутися до Тла - Перемкнутися до Виринального вікна + Перемкнутися до вікна Перемкнутися до Головної Імпортувати базу @@ -204,7 +204,7 @@ Без відео Помилкова ланка URL або інтернет не є доступним Цей дозвіл має бути відкритим -\nу виринальному вікні +\nу вікні «reCAPTCHA» Завантажити @@ -248,7 +248,7 @@ Додати до - Показувати підказку коли фонова чи виринальна кнопка натиснута на сторінці відео деталей + Показувати підказку коли натиснута кнопка фону або вікна, на сторінці інформації відео Перемкнути орієнтацію Фатальна помилка програвача Зовнішні програвачі не підтримують такі види ланок @@ -335,14 +335,14 @@ Ятка Набуває популярності Фоновий програвач - Виринальний програвач + Віконний програвач Усунути Затиснути, аби зняти з черги Зняти з черги у фоновому програвачеві - Зняти з черги у виринальному програвачеві + Зняти з черги у віконному програвачеві Розпочати програвання звідси Розпочати програвання звідси у фоновому програвачеві - Розпочати програвання звідси у виринальному програвачеві + Розпочати програвання у вікні звідси Відчинити шухляду Зачинити шухляду From 96a327af178d5bef5928caae63580a6e9029af64 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Sun, 18 Mar 2018 16:37:49 +0100 Subject: [PATCH 26/69] made frontend combatible to latest extractor refactorings --- app/build.gradle | 2 +- .../database/stream/model/StreamEntity.java | 8 ++-- .../newpipe/download/DownloadDialog.java | 2 +- .../fragments/detail/VideoDetailFragment.java | 11 +++-- .../fragments/list/BaseListInfoFragment.java | 20 ++++---- .../list/channel/ChannelFragment.java | 48 +++++++++---------- .../fragments/list/feed/FeedFragment.java | 6 +-- .../fragments/list/kiosk/KioskFragment.java | 4 +- .../list/playlist/PlaylistFragment.java | 18 +++++-- .../fragments/list/search/SearchFragment.java | 15 +++--- .../subscription/SubscriptionFragment.java | 8 +--- .../newpipe/info_list/InfoItemBuilder.java | 2 +- .../newpipe/info_list/InfoListAdapter.java | 2 +- .../holder/ChannelInfoItemHolder.java | 7 +-- .../holder/ChannelMiniInfoItemHolder.java | 18 +++---- .../holder/PlaylistMiniInfoItemHolder.java | 2 +- .../holder/StreamInfoItemHolder.java | 10 ++-- .../holder/StreamMiniInfoItemHolder.java | 20 ++++---- .../newpipe/player/BackgroundPlayer.java | 6 +-- .../org/schabi/newpipe/player/BasePlayer.java | 4 +- .../newpipe/player/ServicePlayerActivity.java | 2 +- .../schabi/newpipe/player/VideoPlayer.java | 6 +-- .../player/playback/MediaSourceManager.java | 6 +-- .../playlist/AbstractInfoPlayQueue.java | 22 ++++----- .../newpipe/playlist/ChannelPlayQueue.java | 5 +- .../newpipe/playlist/PlaylistPlayQueue.java | 5 +- .../schabi/newpipe/util/ExtractorHelper.java | 12 ++--- 27 files changed, 141 insertions(+), 130 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 88fe38183..9fa911e54 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { exclude module: 'support-annotations' } - implementation 'com.github.TeamNewPipe:NewPipeExtractor:fce324d1bc74bc' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:f787b375e5fb6d' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:1.10.19' diff --git a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.java b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.java index 2fddaa1bb..0a9a0bb66 100644 --- a/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.java +++ b/app/src/main/java/org/schabi/newpipe/database/stream/model/StreamEntity.java @@ -71,14 +71,14 @@ public class StreamEntity implements Serializable { @Ignore public StreamEntity(final StreamInfoItem item) { - this(item.service_id, item.name, item.url, item.stream_type, item.thumbnail_url, - item.uploader_name, item.duration); + this(item.getServiceId(), item.getName(), item.getUrl(), item.getStreamType(), item.getThumbnailUrl(), + item.getUploaderName(), item.getDuration()); } @Ignore public StreamEntity(final StreamInfo info) { - this(info.service_id, info.name, info.url, info.stream_type, info.thumbnail_url, - info.uploader_name, info.duration); + this(info.getServiceId(), info.getName(), info.getUrl(), info.getStreamType(), info.getThumbnailUrl(), + info.getUploaderName(), info.getDuration()); } @Ignore diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java index b217b91b3..9bcd0bcb7 100644 --- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java +++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java @@ -205,7 +205,7 @@ public class DownloadDialog extends DialogFragment implements RadioGroup.OnCheck if (DEBUG) Log.d(TAG, "onCheckedChanged() called with: group = [" + group + "], checkedId = [" + checkedId + "]"); switch (checkedId) { case R.id.audio_button: - setupAudioSpinner(currentInfo.audio_streams, streamsSpinner); + setupAudioSpinner(currentInfo.getAudioStreams(), streamsSpinner); break; case R.id.video_button: setupVideoSpinner(sortedStreamVideosList, streamsSpinner); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 4d935dbce..b3ca5f47f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -383,7 +383,8 @@ public class VideoDetailFragment } break; case R.id.detail_thumbnail_root_layout: - if (currentInfo.video_streams.isEmpty() && currentInfo.video_only_streams.isEmpty()) { + if (currentInfo.getVideoStreams().isEmpty() + && currentInfo.getVideoOnlyStreams().isEmpty()) { openBackgroundPlayer(false); } else { openVideoPlayer(); @@ -618,7 +619,8 @@ public class VideoDetailFragment relatedStreamRootLayout.setVisibility(View.VISIBLE); } else nextStreamTitle.setVisibility(View.GONE); - if (info.related_streams != null && !info.related_streams.isEmpty() && showRelatedStreams) { + if (info.getRelatedStreams() != null + && !info.getRelatedStreams().isEmpty() && showRelatedStreams) { //long first = System.nanoTime(), each; int to = info.getRelatedStreams().size() >= INITIAL_RELATED_VIDEOS ? INITIAL_RELATED_VIDEOS @@ -683,7 +685,7 @@ public class VideoDetailFragment switch (id) { case R.id.menu_item_share: { if(currentInfo != null) { - shareUrl(currentInfo.name, url); + shareUrl(currentInfo.getName(), url); } else { shareUrl(url, url); } @@ -1210,7 +1212,8 @@ public class VideoDetailFragment spinnerToolbar.setVisibility(View.GONE); break; default: - if (!info.video_streams.isEmpty() || !info.video_only_streams.isEmpty()) break; + if (!info.getVideoStreams().isEmpty() + || !info.getVideoOnlyStreams().isEmpty()) break; detailControlsBackground.setVisibility(View.GONE); detailControlsPopup.setVisibility(View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java index 34f190032..a132213bf 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListInfoFragment.java @@ -20,7 +20,7 @@ import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; public abstract class BaseListInfoFragment - extends BaseListFragment { + extends BaseListFragment { @State protected int serviceId = Constants.NO_SERVICE_ID; @@ -117,7 +117,7 @@ public abstract class BaseListInfoFragment .subscribe((@NonNull I result) -> { isLoading.set(false); currentInfo = result; - currentNextPageUrl = result.next_streams_url; + currentNextPageUrl = result.getNextPageUrl(); handleResult(result); }, (@NonNull Throwable throwable) -> onError(throwable)); } @@ -126,7 +126,7 @@ public abstract class BaseListInfoFragment * Implement the logic to load more items
* You can use the default implementations from {@link org.schabi.newpipe.util.ExtractorHelper} */ - protected abstract Single loadMoreItemsLogic(); + protected abstract Single loadMoreItemsLogic(); protected void loadMoreItems() { isLoading.set(true); @@ -135,9 +135,9 @@ public abstract class BaseListInfoFragment currentWorker = loadMoreItemsLogic() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe((@io.reactivex.annotations.NonNull ListExtractor.InfoItemPage InfoItemPage) -> { + .subscribe((@io.reactivex.annotations.NonNull ListExtractor.InfoItemsPage InfoItemsPage) -> { isLoading.set(false); - handleNextItems(InfoItemPage); + handleNextItems(InfoItemsPage); }, (@io.reactivex.annotations.NonNull Throwable throwable) -> { isLoading.set(false); onError(throwable); @@ -145,10 +145,10 @@ public abstract class BaseListInfoFragment } @Override - public void handleNextItems(ListExtractor.InfoItemPage result) { + public void handleNextItems(ListExtractor.InfoItemsPage result) { super.handleNextItems(result); - currentNextPageUrl = result.nextPageUrl; - infoListAdapter.addInfoItemList(result.infoItemList); + currentNextPageUrl = result.getNextPageUrl(); + infoListAdapter.addInfoItemList(result.getItems()); showListFooter(hasMoreItems()); } @@ -171,8 +171,8 @@ public abstract class BaseListInfoFragment setTitle(name); if (infoListAdapter.getItemsList().size() == 0) { - if (result.related_streams.size() > 0) { - infoListAdapter.addInfoItemList(result.related_streams); + if (result.getRelatedItems().size() > 0) { + infoListAdapter.addInfoItemList(result.getRelatedItems()); showListFooter(hasMoreItems()); } else { infoListAdapter.clearStreamItemList(); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 3261e6dad..7783d8a98 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -27,10 +27,13 @@ import com.jakewharton.rxbinding2.view.RxView; import org.schabi.newpipe.R; import org.schabi.newpipe.database.subscription.SubscriptionEntity; +import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.stream.Stream; +import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.info_list.InfoItemDialog; @@ -44,6 +47,7 @@ import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -388,7 +392,7 @@ public class ChannelFragment extends BaseListInfoFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - protected Single loadMoreItemsLogic() { + protected Single loadMoreItemsLogic() { return ExtractorHelper.getMoreChannelItems(serviceId, url, currentNextPageUrl); } @@ -415,8 +419,8 @@ public class ChannelFragment extends BaseListInfoFragment { super.handleResult(result); headerRootLayout.setVisibility(View.VISIBLE); - imageLoader.displayImage(result.banner_url, headerChannelBanner, DISPLAY_BANNER_OPTIONS); - imageLoader.displayImage(result.avatar_url, headerAvatarView, DISPLAY_AVATAR_OPTIONS); + imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner, DISPLAY_BANNER_OPTIONS); + imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView, DISPLAY_AVATAR_OPTIONS); if (result.getSubscriberCount() != -1) { headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount())); @@ -427,8 +431,8 @@ public class ChannelFragment extends BaseListInfoFragment { playlistCtrl.setVisibility(View.VISIBLE); - if (!result.errors.isEmpty()) { - showSnackBarError(result.errors, UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); + if (!result.getErrors().isEmpty()) { + showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); } if (disposables != null) disposables.clear(); @@ -436,24 +440,12 @@ public class ChannelFragment extends BaseListInfoFragment { updateSubscription(result); monitorSubscription(result); - headerPlayAllButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - NavigationHelper.playOnMainPlayer(activity, getPlayQueue()); - } - }); - headerPopupButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - NavigationHelper.playOnPopupPlayer(activity, getPlayQueue()); - } - }); - headerBackgroundButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue()); - } - }); + headerPlayAllButton.setOnClickListener( + view -> NavigationHelper.playOnMainPlayer(activity, getPlayQueue())); + headerPopupButton.setOnClickListener( + view -> NavigationHelper.playOnPopupPlayer(activity, getPlayQueue())); + headerBackgroundButton.setOnClickListener( + view -> NavigationHelper.playOnBackgroundPlayer(activity, getPlayQueue())); } private PlayQueue getPlayQueue() { @@ -461,17 +453,23 @@ public class ChannelFragment extends BaseListInfoFragment { } private PlayQueue getPlayQueue(final int index) { + final List streamItems = new ArrayList<>(); + for(InfoItem i : infoListAdapter.getItemsList()) { + if(i instanceof StreamInfoItem) { + streamItems.add((StreamInfoItem) i); + } + } return new ChannelPlayQueue( currentInfo.getServiceId(), currentInfo.getUrl(), currentInfo.getNextPageUrl(), - infoListAdapter.getItemsList(), + streamItems, index ); } @Override - public void handleNextItems(ListExtractor.InfoItemPage result) { + public void handleNextItems(ListExtractor.InfoItemsPage result) { super.handleNextItems(result); if (!result.getErrors().isEmpty()) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java index 57841cb87..dabfd9e1b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/feed/FeedFragment.java @@ -297,12 +297,12 @@ public class FeedFragment extends BaseListFragment, Voi // Called only when response is non-empty @Override public void onSuccess(final ChannelInfo channelInfo) { - if (infoListAdapter == null || channelInfo.getRelatedStreams().isEmpty()) { + if (infoListAdapter == null || channelInfo.getRelatedItems().isEmpty()) { onDone(); return; } - final InfoItem item = channelInfo.getRelatedStreams().get(0); + final InfoItem item = channelInfo.getRelatedItems().get(0); // Keep requesting new items if the current one already exists boolean itemExists = doesItemExist(infoListAdapter.getItemsList(), item); if (!itemExists) { @@ -411,7 +411,7 @@ public class FeedFragment extends BaseListFragment, Voi private boolean doesItemExist(final List items, final InfoItem item) { for (final InfoItem existingItem : items) { - if (existingItem.info_type == item.info_type && + if (existingItem.getInfoType() == item.getInfoType() && existingItem.getServiceId() == item.getServiceId() && existingItem.getName().equals(item.getName()) && existingItem.getUrl().equals(item.getUrl())) return true; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java index 976bcced2..482f71cb4 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.java @@ -141,7 +141,7 @@ public class KioskFragment extends BaseListInfoFragment { } @Override - public Single loadMoreItemsLogic() { + public Single loadMoreItemsLogic() { String contentCountry = PreferenceManager .getDefaultSharedPreferences(activity) .getString(getString(R.string.content_country_key), @@ -174,7 +174,7 @@ public class KioskFragment extends BaseListInfoFragment { } @Override - public void handleNextItems(ListExtractor.InfoItemPage result) { + public void handleNextItems(ListExtractor.InfoItemsPage result) { super.handleNextItems(result); if (!result.getErrors().isEmpty()) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 324d3d7ef..9033560bd 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -22,10 +22,12 @@ import org.reactivestreams.Subscription; import org.schabi.newpipe.NewPipeDatabase; import org.schabi.newpipe.R; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; +import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.ListExtractor; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; +import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.fragments.list.BaseListInfoFragment; import org.schabi.newpipe.fragments.local.RemotePlaylistManager; @@ -38,6 +40,7 @@ import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -206,7 +209,7 @@ public class PlaylistFragment extends BaseListInfoFragment { //////////////////////////////////////////////////////////////////////////*/ @Override - protected Single loadMoreItemsLogic() { + protected Single loadMoreItemsLogic() { return ExtractorHelper.getMorePlaylistItems(serviceId, url, currentNextPageUrl); } @@ -269,7 +272,8 @@ public class PlaylistFragment extends BaseListInfoFragment { playlistCtrl.setVisibility(View.VISIBLE); imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS); - headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, (int) result.stream_count, (int) result.stream_count)); + headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, + (int) result.getStreamCount(), (int) result.getStreamCount())); if (!result.getErrors().isEmpty()) { showSnackBarError(result.getErrors(), UserAction.REQUESTED_PLAYLIST, NewPipe.getNameOfService(result.getServiceId()), result.getUrl(), 0); @@ -297,17 +301,23 @@ public class PlaylistFragment extends BaseListInfoFragment { } private PlayQueue getPlayQueue(final int index) { + final List infoItems = new ArrayList<>(); + for(InfoItem i : infoListAdapter.getItemsList()) { + if(i instanceof StreamInfoItem) { + infoItems.add((StreamInfoItem) i); + } + } return new PlaylistPlayQueue( currentInfo.getServiceId(), currentInfo.getUrl(), currentInfo.getNextPageUrl(), - infoListAdapter.getItemsList(), + infoItems, index ); } @Override - public void handleNextItems(ListExtractor.InfoItemPage result) { + public void handleNextItems(ListExtractor.InfoItemsPage result) { super.handleNextItems(result); if (!result.getErrors().isEmpty()) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index 1ad31d06c..f7831e02d 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -71,7 +71,9 @@ import io.reactivex.subjects.PublishSubject; import static org.schabi.newpipe.util.AnimationUtils.animateView; -public class SearchFragment extends BaseListFragment implements BackPressable { +public class SearchFragment + extends BaseListFragment + implements BackPressable { /*////////////////////////////////////////////////////////////////////////// // Search @@ -759,12 +761,7 @@ public class SearchFragment extends BaseListFragment suggestions) { if (DEBUG) Log.d(TAG, "handleSuggestions() called with: suggestions = [" + suggestions + "]"); suggestionsRecyclerView.smoothScrollToPosition(0); - suggestionsRecyclerView.post(new Runnable() { - @Override - public void run() { - suggestionListAdapter.setItems(suggestions); - } - }); + suggestionsRecyclerView.post(() -> suggestionListAdapter.setItems(suggestions)); if (errorPanelRoot.getVisibility() == View.VISIBLE) { hideLoading(); @@ -822,10 +819,10 @@ public class SearchFragment extends BaseListFragment items = new ArrayList<>(); for (final SubscriptionEntity subscription : subscriptions) items.add(subscription.toChannelInfoItem()); - Collections.sort(items, new Comparator() { - @Override - public int compare(InfoItem o1, InfoItem o2) { - return o1.name.compareToIgnoreCase(o2.name); - } - }); + Collections.sort(items, + (InfoItem o1, InfoItem o2) -> o1.getName().compareToIgnoreCase(o2.getName())); return items; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java index 218895983..78867c81f 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java @@ -60,7 +60,7 @@ public class InfoItemBuilder { } public View buildView(@NonNull ViewGroup parent, @NonNull final InfoItem infoItem, boolean useMiniVariant) { - InfoItemHolder holder = holderFromInfoType(parent, infoItem.info_type, useMiniVariant); + InfoItemHolder holder = holderFromInfoType(parent, infoItem.getInfoType(), useMiniVariant); holder.updateFromItem(infoItem); return holder.itemView; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java index 4b9914397..9b3405484 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java @@ -203,7 +203,7 @@ public class InfoListAdapter extends RecyclerView.Adapter= 0) { - String formattedVideoAmount = Localization.localizeStreamCount(itemBuilder.getContext(), item.stream_count); + if (item.getStreamCount() >= 0) { + String formattedVideoAmount = Localization.localizeStreamCount(itemBuilder.getContext(), + item.getStreamCount()); if (!details.isEmpty()) { details += " • " + formattedVideoAmount; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 48fb18517..211fa60cd 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -40,22 +40,22 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemAdditionalDetailView.setText(getDetailLine(item)); itemBuilder.getImageLoader() - .displayImage(item.thumbnail_url, itemThumbnailView, ChannelInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); + .displayImage(item.getThumbnailUrl(), + itemThumbnailView, + ChannelInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); - itemView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (itemBuilder.getOnChannelSelectedListener() != null) { - itemBuilder.getOnChannelSelectedListener().selected(item); - } + itemView.setOnClickListener(view -> { + if (itemBuilder.getOnChannelSelectedListener() != null) { + itemBuilder.getOnChannelSelectedListener().selected(item); } }); } protected String getDetailLine(final ChannelInfoItem item) { String details = ""; - if (item.subscriber_count >= 0) { - details += Localization.shortSubscriberCount(itemBuilder.getContext(), item.subscriber_count); + if (item.getSubscriberCount() >= 0) { + details += Localization.shortSubscriberCount(itemBuilder.getContext(), + item.getSubscriberCount()); } return details; } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index 50b551c61..30d84e1bd 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -40,7 +40,7 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder { itemUploaderView.setText(item.getUploaderName()); itemBuilder.getImageLoader() - .displayImage(item.thumbnail_url, itemThumbnailView, DISPLAY_THUMBNAIL_OPTIONS); + .displayImage(item.getThumbnailUrl(), itemThumbnailView, DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnPlaylistSelectedListener() != null) { diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java index 78954a2ee..0a7705427 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamInfoItemHolder.java @@ -51,14 +51,14 @@ public class StreamInfoItemHolder extends StreamMiniInfoItemHolder { private String getStreamInfoDetailLine(final StreamInfoItem infoItem) { String viewsAndDate = ""; - if (infoItem.view_count >= 0) { - viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.view_count); + if (infoItem.getViewCount() >= 0) { + viewsAndDate = Localization.shortViewCount(itemBuilder.getContext(), infoItem.getViewCount()); } - if (!TextUtils.isEmpty(infoItem.upload_date)) { + if (!TextUtils.isEmpty(infoItem.getUploadDate())) { if (viewsAndDate.isEmpty()) { - viewsAndDate = infoItem.upload_date; + viewsAndDate = infoItem.getUploadDate(); } else { - viewsAndDate += " • " + infoItem.upload_date; + viewsAndDate += " • " + infoItem.getUploadDate(); } } return viewsAndDate; diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 594a85582..72c2830e1 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -41,15 +41,17 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { final StreamInfoItem item = (StreamInfoItem) infoItem; itemVideoTitleView.setText(item.getName()); - itemUploaderView.setText(item.uploader_name); + itemUploaderView.setText(item.getUploaderName()); - if (item.duration > 0) { - itemDurationView.setText(Localization.getDurationString(item.duration)); - itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), R.color.duration_background_color)); + if (item.getDuration() > 0) { + itemDurationView.setText(Localization.getDurationString(item.getDuration())); + itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), + R.color.duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); - } else if (item.stream_type == StreamType.LIVE_STREAM) { + } else if (item.getStreamType() == StreamType.LIVE_STREAM) { itemDurationView.setText(R.string.duration_live); - itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), R.color.live_duration_background_color)); + itemDurationView.setBackgroundColor(ContextCompat.getColor(itemBuilder.getContext(), + R.color.live_duration_background_color)); itemDurationView.setVisibility(View.VISIBLE); } else { itemDurationView.setVisibility(View.GONE); @@ -57,7 +59,9 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { // Default thumbnail is shown on error, while loading and if the url is empty itemBuilder.getImageLoader() - .displayImage(item.thumbnail_url, itemThumbnailView, StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); + .displayImage(item.getThumbnailUrl(), + itemThumbnailView, + StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnStreamSelectedListener() != null) { @@ -65,7 +69,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { } }); - switch (item.stream_type) { + switch (item.getStreamType()) { case AUDIO_STREAM: case VIDEO_STREAM: case LIVE_STREAM: diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 06b62f46f..61720c6b4 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -407,10 +407,10 @@ public final class BackgroundPlayer extends Service { final MediaSource liveSource = super.sourceOf(item, info); if (liveSource != null) return liveSource; - final int index = ListHelper.getDefaultAudioFormat(context, info.audio_streams); - if (index < 0 || index >= info.audio_streams.size()) return null; + final int index = ListHelper.getDefaultAudioFormat(context, info.getAudioStreams()); + if (index < 0 || index >= info.getAudioStreams().size()) return null; - final AudioStream audio = info.audio_streams.get(index); + final AudioStream audio = info.getAudioStreams().get(index); return buildMediaSource(audio.getUrl(), PlayerHelper.cacheKeyOf(info, audio), MediaFormat.getSuffixById(audio.getFormatId())); } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index d5ba7bb86..cee885e22 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -833,7 +833,7 @@ public abstract class BasePlayer implements // on metadata changed } else if (currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) { - final long startPos = info != null ? info.start_position : C.TIME_UNSET; + final long startPos = info != null ? info.getStartPosition() : C.TIME_UNSET; if (DEBUG) Log.d(TAG, "Rewinding to correct" + " window=[" + currentPlayQueueIndex + "]," + " at=[" + getTimeString((int)startPos) + "]," + @@ -950,7 +950,7 @@ public abstract class BasePlayer implements /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds, restart current track. * Also restart the track if the current track is the first in a queue.*/ if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT || playQueue.getIndex() == 0) { - final long startPos = currentInfo == null ? 0 : currentInfo.start_position; + final long startPos = currentInfo == null ? 0 : currentInfo.getStartPosition(); simpleExoPlayer.seekTo(startPos); } else { playQueue.offsetIndex(-1); diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index d9c04b796..c68133094 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -579,7 +579,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity public void onMetadataUpdate(StreamInfo info) { if (info != null) { metadataTitle.setText(info.getName()); - metadataArtist.setText(info.uploader_name); + metadataArtist.setText(info.getUploaderName()); progressEndTime.setVisibility(View.GONE); progressLiveSync.setVisibility(View.GONE); diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index aa90b7b88..48b13654c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -354,10 +354,10 @@ public abstract class VideoPlayer extends BasePlayer break; case VIDEO_STREAM: - if (info.video_streams.size() + info.video_only_streams.size() == 0) break; + if (info.getVideoStreams().size() + info.getVideoOnlyStreams().size() == 0) break; final List videos = ListHelper.getSortedStreamVideosList(context, - info.video_streams, info.video_only_streams, false); + info.getVideoStreams(), info.getVideoOnlyStreams(), false); availableStreams = new ArrayList<>(videos); if (playbackQuality == null) { selectedStreamIndex = getDefaultResolutionIndex(videos); @@ -388,7 +388,7 @@ public abstract class VideoPlayer extends BasePlayer // Create video stream source final List videos = ListHelper.getSortedStreamVideosList(context, - info.video_streams, info.video_only_streams, false); + info.getVideoStreams(), info.getVideoOnlyStreams(), false); final int index; if (videos.isEmpty()) { index = -1; diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index cb803dcd1..ea13a28e7 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -417,9 +417,9 @@ public class MediaSourceManager { final Exception exception = new IllegalStateException( "Unable to resolve source from stream info." + " URL: " + stream.getUrl() + - ", audio count: " + streamInfo.audio_streams.size() + - ", video count: " + streamInfo.video_only_streams.size() + - streamInfo.video_streams.size()); + ", audio count: " + streamInfo.getAudioStreams().size() + + ", video count: " + streamInfo.getVideoOnlyStreams().size() + + streamInfo.getVideoStreams().size()); return new FailedMediaSource(stream, exception); } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java index 6e63a3aaa..2b31cd340 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/AbstractInfoPlayQueue.java @@ -26,13 +26,13 @@ abstract class AbstractInfoPlayQueue ext transient Disposable fetchReactor; AbstractInfoPlayQueue(final U item) { - this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0); + this(item.getServiceId(), item.getUrl(), null, Collections.emptyList(), 0); } AbstractInfoPlayQueue(final int serviceId, final String url, final String nextPageUrl, - final List streams, + final List streams, final int index) { super(index, extractListItems(streams)); @@ -65,10 +65,10 @@ abstract class AbstractInfoPlayQueue ext @Override public void onSuccess(@NonNull T result) { isInitial = false; - if (!result.has_more_streams) isComplete = true; - nextUrl = result.next_streams_url; + if (!result.hasNextPage()) isComplete = true; + nextUrl = result.getNextPageUrl(); - append(extractListItems(result.related_streams)); + append(extractListItems(result.getRelatedItems())); fetchReactor.dispose(); fetchReactor = null; @@ -83,8 +83,8 @@ abstract class AbstractInfoPlayQueue ext }; } - SingleObserver getNextPageObserver() { - return new SingleObserver() { + SingleObserver getNextPageObserver() { + return new SingleObserver() { @Override public void onSubscribe(@NonNull Disposable d) { if (isComplete || isInitial || (fetchReactor != null && !fetchReactor.isDisposed())) { @@ -95,11 +95,11 @@ abstract class AbstractInfoPlayQueue ext } @Override - public void onSuccess(@NonNull ListExtractor.InfoItemPage result) { + public void onSuccess(@NonNull ListExtractor.InfoItemsPage result) { if (!result.hasNextPage()) isComplete = true; - nextUrl = result.nextPageUrl; + nextUrl = result.getNextPageUrl(); - append(extractListItems(result.infoItemList)); + append(extractListItems(result.getItems())); fetchReactor.dispose(); fetchReactor = null; @@ -121,7 +121,7 @@ abstract class AbstractInfoPlayQueue ext fetchReactor = null; } - private static List extractListItems(final List infos) { + private static List extractListItems(final List infos) { List result = new ArrayList<>(); for (final InfoItem stream : infos) { if (stream instanceof StreamInfoItem) { diff --git a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java index a5ecad027..d37b84072 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/ChannelPlayQueue.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.playlist; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.util.ExtractorHelper; import java.util.List; @@ -16,13 +17,13 @@ public final class ChannelPlayQueue extends AbstractInfoPlayQueue streams, + final List streams, final int index) { super(serviceId, url, nextPageUrl, streams, index); } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java index c9340afad..d9e1d2d2b 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlaylistPlayQueue.java @@ -3,6 +3,7 @@ package org.schabi.newpipe.playlist; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; +import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.util.ExtractorHelper; import java.util.List; @@ -16,13 +17,13 @@ public final class PlaylistPlayQueue extends AbstractInfoPlayQueue streams, + final List streams, final int index) { super(serviceId, url, nextPageUrl, streams, index); } diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java index 12c3dc44c..1897589c6 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java @@ -29,7 +29,7 @@ import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; import org.schabi.newpipe.ReCaptchaActivity; import org.schabi.newpipe.extractor.Info; -import org.schabi.newpipe.extractor.ListExtractor.InfoItemPage; +import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.channel.ChannelInfo; import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException; @@ -78,7 +78,7 @@ public final class ExtractorHelper { ); } - public static Single getMoreSearchItems(final int serviceId, + public static Single getMoreSearchItems(final int serviceId, final String query, final int nextPageNumber, final String searchLanguage, @@ -86,7 +86,7 @@ public final class ExtractorHelper { checkServiceId(serviceId); return searchFor(serviceId, query, nextPageNumber, searchLanguage, filter) .map((@NonNull SearchResult searchResult) -> - new InfoItemPage(searchResult.resultList, + new InfoItemsPage(searchResult.resultList, nextPageNumber + "", searchResult.errors)); } @@ -117,7 +117,7 @@ public final class ExtractorHelper { ChannelInfo.getInfo(NewPipe.getService(serviceId), url))); } - public static Single getMoreChannelItems(final int serviceId, + public static Single getMoreChannelItems(final int serviceId, final String url, final String nextStreamsUrl) { checkServiceId(serviceId); @@ -133,7 +133,7 @@ public final class ExtractorHelper { PlaylistInfo.getInfo(NewPipe.getService(serviceId), url))); } - public static Single getMorePlaylistItems(final int serviceId, + public static Single getMorePlaylistItems(final int serviceId, final String url, final String nextStreamsUrl) { checkServiceId(serviceId); @@ -149,7 +149,7 @@ public final class ExtractorHelper { KioskInfo.getInfo(NewPipe.getService(serviceId), url, contentCountry))); } - public static Single getMoreKioskItems(final int serviceId, + public static Single getMoreKioskItems(final int serviceId, final String url, final String nextStreamsUrl, final String contentCountry) { From a32273af914bfd82d5ba0e1f48aa8b6a6754ecb2 Mon Sep 17 00:00:00 2001 From: ezjerry liao Date: Mon, 19 Mar 2018 11:40:49 +0000 Subject: [PATCH 27/69] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (343 of 343 strings) --- app/src/main/res/values-zh-rTW/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index c32a6f272..4517da74b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -419,4 +419,11 @@ 請記住,此操作可能會造成網路昂貴花費。 \n \n您想繼續嗎? - +要匯入您的 SoundCloud,您必須知道您的個人資料網址或 ID。 如果您這樣做,只需在下方的輸入中鍵入其中的任意一個,然後就可以開始了。 +\n +\n如果您不這樣做,您可以按照以下步驟操作: +\n1. 在一些瀏覽器中啟用「桌面模式」(該網站不適用於行動裝置) +\n2. 移至此網址:%1$s +\n3. 詢問時登入到您的帳號 +\n4. 複製網址您會被重新導向(這是您的個人資料網址) + From 12ce915e8ef3741759106edc57d6d20d07b0d7f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Allan=20Nordh=C3=B8y?= Date: Sun, 18 Mar 2018 13:50:16 +0000 Subject: [PATCH 28/69] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 93.2% (320 of 343 strings) --- app/src/main/res/values-nb-rNO/strings.xml | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index c214f5bd1..df5221bc4 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -382,4 +382,26 @@ Mindre skrift Normal skrift Større skrift - +Bruk raskt unøyaktig søk + Feilretting + Fil + + Ugyldig mappe + Ugyldig fil/innholdskilde + Filen finnes ikke eller så har du ikke tilgang til å lese eller skrive til den + Filnavn kan ikke være tomt + En feil inntraff: %1$s + + Auto-generert + Skru på LeakCanary + Importer + Importer fra + Eksporter til + + Importerer… + Eksporterer… + + Importer fil + Forrige eksport + + From 61b422502b4baab2c1a33693bfdcc0d0d7d2bad4 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 13 Mar 2018 20:25:22 -0700 Subject: [PATCH 29/69] -[#1060] Added toggle to disable thumbnail loading. -Added button to wipe metadata cache. -Added more paddings on player buttons. -Added new animations to main player secondary controls and play queue expand/collapse. -Refactored play queue item touch callback for use in all players. -Improved MediaSourceManager to better handle expired stream reloading. -[#1186] Changed live sync button text to "LIVE". -Removed MediaSourceManager loader coupling on main players. -Moved service dependent expiry resolution to ServiceHelper. -[#1186] Fixed livestream timeline updates causing negative time position. -[#1186] Fixed livestream not starting from live-edge. -Fixed main player system UI not retracting on playback start. --- .../org/schabi/newpipe/ImageDownloader.java | 23 +++- .../org/schabi/newpipe/player/BasePlayer.java | 81 ++++++++----- .../newpipe/player/MainVideoPlayer.java | 53 +++------ .../newpipe/player/ServicePlayerActivity.java | 40 +------ .../player/playback/MediaSourceManager.java | 111 ++++++++++-------- .../player/playback/PlaybackListener.java | 10 ++ .../playlist/PlayQueueItemTouchCallback.java | 52 ++++++++ .../settings/HistorySettingsFragment.java | 23 ++++ .../org/schabi/newpipe/util/InfoCache.java | 9 +- .../schabi/newpipe/util/ServiceHelper.java | 12 ++ .../activity_player_queue_control.xml | 6 +- .../main/res/layout/activity_main_player.xml | 16 ++- .../layout/activity_player_queue_control.xml | 6 +- app/src/main/res/layout/player_popup.xml | 6 +- app/src/main/res/values/settings_keys.xml | 4 + app/src/main/res/values/strings.xml | 21 ++-- app/src/main/res/xml/content_settings.xml | 6 + app/src/main/res/xml/history_settings.xml | 5 + 18 files changed, 305 insertions(+), 179 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemTouchCallback.java diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java index 5ea067d00..8baabed6b 100644 --- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java +++ b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java @@ -1,25 +1,40 @@ package org.schabi.newpipe; import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import com.nostra13.universalimageloader.core.download.BaseImageDownloader; import org.schabi.newpipe.extractor.NewPipe; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; public class ImageDownloader extends BaseImageDownloader { + private static final ByteArrayInputStream DUMMY_INPUT_STREAM = + new ByteArrayInputStream(new byte[]{}); + + private final SharedPreferences preferences; + private final String downloadThumbnailKey; + public ImageDownloader(Context context) { super(context); + this.preferences = PreferenceManager.getDefaultSharedPreferences(context); + this.downloadThumbnailKey = context.getString(R.string.download_thumbnail_key); } - public ImageDownloader(Context context, int connectTimeout, int readTimeout) { - super(context, connectTimeout, readTimeout); + private boolean isDownloadingThumbnail() { + return preferences.getBoolean(downloadThumbnailKey, true); } protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException { - Downloader downloader = (Downloader) NewPipe.getDownloader(); - return downloader.stream(imageUri); + if (isDownloadingThumbnail()) { + final Downloader downloader = (Downloader) NewPipe.getDownloader(); + return downloader.stream(imageUri); + } else { + return DUMMY_INPUT_STREAM; + } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index cee885e22..5355e19ee 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -57,6 +57,7 @@ import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.history.HistoryRecordManager; import org.schabi.newpipe.player.helper.AudioReactor; import org.schabi.newpipe.player.helper.LoadController; @@ -244,6 +245,7 @@ public abstract class BasePlayer implements playQueue = queue; playQueue.init(); + if (playbackManager != null) playbackManager.dispose(); playbackManager = new MediaSourceManager(this, playQueue); if (playQueueAdapter != null) playQueueAdapter.dispose(); @@ -272,7 +274,6 @@ public abstract class BasePlayer implements public void destroy() { if (DEBUG) Log.d(TAG, "destroy() called"); destroyPlayer(); - clearThumbnailCache(); unregisterBroadcastReceiver(); trackSelector = null; @@ -314,11 +315,6 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "Thumbnail - onLoadingCancelled() called with: " + "imageUri = [" + imageUri + "], view = [" + view + "]"); } - - protected void clearThumbnailCache() { - ImageLoader.getInstance().clearMemoryCache(); - } - /*////////////////////////////////////////////////////////////////////////// // MediaSource Building //////////////////////////////////////////////////////////////////////////*/ @@ -448,7 +444,6 @@ public abstract class BasePlayer implements public void onPlaying() { if (DEBUG) Log.d(TAG, "onPlaying() called"); if (!isProgressLoopRunning()) startProgressLoop(); - if (!isCurrentWindowValid()) seekToDefault(); } public void onBuffering() {} @@ -522,11 +517,9 @@ public abstract class BasePlayer implements ); } - private Disposable getProgressReactor() { return Observable.interval(PROGRESS_LOOP_INTERVAL, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) - .filter(ignored -> isProgressLoopRunning()) .subscribe(ignored -> triggerProgressUpdate()); } @@ -541,16 +534,19 @@ public abstract class BasePlayer implements (manifest == null ? "no manifest" : "available manifest") + ", " + "timeline size = [" + timeline.getWindowCount() + "], " + "reason = [" + reason + "]"); + if (playQueue == null) return; switch (reason) { case Player.TIMELINE_CHANGE_REASON_RESET: // called after #block case Player.TIMELINE_CHANGE_REASON_PREPARED: // called after #unblock case Player.TIMELINE_CHANGE_REASON_DYNAMIC: // called after playlist changes - if (playQueue != null && playbackManager != null && - // ensures MediaSourceManager#update is complete - timeline.getWindowCount() == playQueue.size()) { - playbackManager.load(); + // ensures MediaSourceManager#update is complete + final boolean isPlaylistStable = timeline.getWindowCount() == playQueue.size(); + // Ensure dynamic/livestream timeline changes does not cause negative position + if (isPlaylistStable && !isCurrentWindowValid()) { + simpleExoPlayer.seekTo(/*clampToMillis=*/0); } + break; } } @@ -775,6 +771,16 @@ public abstract class BasePlayer implements // Playback Listener //////////////////////////////////////////////////////////////////////////*/ + @Override + public boolean isNearPlaybackEdge(final long timeToEndMillis) { + // If live, then not near playback edge + if (simpleExoPlayer == null || simpleExoPlayer.isCurrentWindowDynamic()) return false; + + final long currentPositionMillis = simpleExoPlayer.getCurrentPosition(); + final long currentDurationMillis = simpleExoPlayer.getDuration(); + return currentDurationMillis - currentPositionMillis < timeToEndMillis; + } + @Override public void onPlaybackBlock() { if (simpleExoPlayer == null) return; @@ -796,7 +802,6 @@ public abstract class BasePlayer implements if (getCurrentState() == STATE_BLOCKED) changeState(STATE_BUFFERING); simpleExoPlayer.prepare(mediaSource); - seekToDefault(); } @Override @@ -825,16 +830,24 @@ public abstract class BasePlayer implements if (simpleExoPlayer == null) return; final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex(); + final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount(); // Check if on wrong window if (currentPlayQueueIndex != playQueue.getIndex()) { - Log.e(TAG, "Play Queue may be desynchronized: item " + + Log.e(TAG, "Playback - Play Queue may be desynchronized: item " + "index=[" + currentPlayQueueIndex + "], " + "queue index=[" + playQueue.getIndex() + "]"); - // on metadata changed + // Check if bad seek position + } else if ((currentPlaylistSize > 0 && currentPlayQueueIndex > currentPlaylistSize) || + currentPlaylistIndex < 0) { + Log.e(TAG, "Playback - Trying to seek to " + + "index=[" + currentPlayQueueIndex + "] with " + + "playlist length=[" + currentPlaylistSize + "]"); + + // If not playing correct stream, change window position } else if (currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) { final long startPos = info != null ? info.getStartPosition() : C.TIME_UNSET; - if (DEBUG) Log.d(TAG, "Rewinding to correct" + + if (DEBUG) Log.d(TAG, "Playback - Rewinding to correct" + " window=[" + currentPlayQueueIndex + "]," + " at=[" + getTimeString((int)startPos) + "]," + " from=[" + simpleExoPlayer.getCurrentWindowIndex() + "]."); @@ -858,6 +871,11 @@ public abstract class BasePlayer implements @Nullable @Override public MediaSource sourceOf(PlayQueueItem item, StreamInfo info) { + final StreamType streamType = info.getStreamType(); + if (!(streamType == StreamType.AUDIO_LIVE_STREAM || streamType == StreamType.LIVE_STREAM)) { + return null; + } + if (!info.getHlsUrl().isEmpty()) { return buildLiveMediaSource(info.getHlsUrl(), C.TYPE_HLS); } else if (!info.getDashMpdUrl().isEmpty()) { @@ -909,6 +927,9 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); if (playWhenReady) audioReactor.requestAudioFocus(); changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); + + // On live prepared + if (simpleExoPlayer.isCurrentWindowDynamic()) seekToDefault(); } public void onVideoPlayPause() { @@ -945,14 +966,15 @@ public abstract class BasePlayer implements if (simpleExoPlayer == null || playQueue == null) return; if (DEBUG) Log.d(TAG, "onPlayPrevious() called"); - savePlaybackState(); - - /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds, restart current track. - * Also restart the track if the current track is the first in a queue.*/ - if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT || playQueue.getIndex() == 0) { - final long startPos = currentInfo == null ? 0 : currentInfo.getStartPosition(); - simpleExoPlayer.seekTo(startPos); + /* If current playback has run for PLAY_PREV_ACTIVATION_LIMIT milliseconds, + * restart current track. Also restart the track if the current track + * is the first in a queue.*/ + if (simpleExoPlayer.getCurrentPosition() > PLAY_PREV_ACTIVATION_LIMIT || + playQueue.getIndex() == 0) { + seekToDefault(); + playQueue.offsetIndex(0); } else { + savePlaybackState(); playQueue.offsetIndex(-1); } } @@ -962,7 +984,6 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "onPlayNext() called"); savePlaybackState(); - playQueue.offsetIndex(+1); } @@ -975,8 +996,9 @@ public abstract class BasePlayer implements if (playQueue.getIndex() == index && simpleExoPlayer.getCurrentWindowIndex() == index) { seekToDefault(); } else { - playQueue.setIndex(index); + savePlaybackState(); } + playQueue.setIndex(index); } public void seekBy(int milliSeconds) { @@ -1015,8 +1037,11 @@ public abstract class BasePlayer implements protected void reload() { if (playbackManager != null) { - playbackManager.reset(); - playbackManager.load(); + playbackManager.dispose(); + } + + if (playQueue != null) { + playbackManager = new MediaSourceManager(this, playQueue); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 4f27d1fee..dd7e0c71e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -62,6 +62,7 @@ import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItemBuilder; import org.schabi.newpipe.playlist.PlayQueueItemHolder; +import org.schabi.newpipe.playlist.PlayQueueItemTouchCallback; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.NavigationHelper; @@ -76,6 +77,8 @@ import java.util.UUID; import static org.schabi.newpipe.player.BasePlayer.STATE_PLAYING; import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_DURATION; import static org.schabi.newpipe.player.VideoPlayer.DEFAULT_CONTROLS_HIDE_TIME; +import static org.schabi.newpipe.util.AnimationUtils.Type.SLIDE_AND_ALPHA; +import static org.schabi.newpipe.util.AnimationUtils.animateRotation; import static org.schabi.newpipe.util.AnimationUtils.animateView; import static org.schabi.newpipe.util.StateSaver.KEY_SAVED_STATE; @@ -110,7 +113,7 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) getWindow().setStatusBarColor(Color.BLACK); setVolumeControlStream(AudioManager.STREAM_MUSIC); - changeSystemUi(); + hideSystemUi(); setContentView(R.layout.activity_main_player); playerImpl = new VideoPlayerImpl(this); playerImpl.setup(findViewById(android.R.id.content)); @@ -597,28 +600,27 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR updatePlaybackButtons(); getControlsRoot().setVisibility(View.INVISIBLE); - queueLayout.setVisibility(View.VISIBLE); + animateView(queueLayout, SLIDE_AND_ALPHA, /*visible=*/true, + DEFAULT_CONTROLS_DURATION); itemsList.scrollToPosition(playQueue.getIndex()); } private void onQueueClosed() { - queueLayout.setVisibility(View.GONE); + animateView(queueLayout, SLIDE_AND_ALPHA, /*visible=*/false, + DEFAULT_CONTROLS_DURATION); queueVisible = false; } private void onMoreOptionsClicked() { if (DEBUG) Log.d(TAG, "onMoreOptionsClicked() called"); - if (secondaryControls.getVisibility() == View.VISIBLE) { - moreOptionsButton.setImageDrawable(getResources().getDrawable( - R.drawable.ic_expand_more_white_24dp)); - animateView(secondaryControls, false, 200); - } else { - moreOptionsButton.setImageDrawable(getResources().getDrawable( - R.drawable.ic_expand_less_white_24dp)); - animateView(secondaryControls, true, 200); - } + final boolean isMoreControlsVisible = secondaryControls.getVisibility() == View.VISIBLE; + + animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION, + isMoreControlsVisible ? 0 : 180); + animateView(secondaryControls, SLIDE_AND_ALPHA, !isMoreControlsVisible, + DEFAULT_CONTROLS_DURATION); showControls(DEFAULT_CONTROLS_DURATION); } @@ -696,7 +698,6 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR animatePlayButtons(true, 200); }); - changeSystemUi(); getRootView().setKeepScreenOn(true); } @@ -798,31 +799,11 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR } private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { + return new PlayQueueItemTouchCallback() { @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) { - if (source.getItemViewType() != target.getItemViewType()) { - return false; - } - - final int sourceIndex = source.getLayoutPosition(); - final int targetIndex = target.getLayoutPosition(); - playQueue.move(sourceIndex, targetIndex); - return true; + public void onMove(int sourceIndex, int targetIndex) { + if (playQueue != null) playQueue.move(sourceIndex, targetIndex); } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - - @Override - public boolean isItemViewSwipeEnabled() { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {} }; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index c68133094..1c3ffe911 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -34,6 +34,7 @@ import org.schabi.newpipe.player.event.PlayerEventListener; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItemBuilder; import org.schabi.newpipe.playlist.PlayQueueItemHolder; +import org.schabi.newpipe.playlist.PlayQueueItemTouchCallback; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -61,9 +62,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; - private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 10; - private static final int MAXIMUM_INITIAL_DRAG_VELOCITY = 25; - private View rootView; private RecyclerView itemsList; @@ -398,43 +396,11 @@ public abstract class ServicePlayerActivity extends AppCompatActivity } private ItemTouchHelper.SimpleCallback getItemTouchCallback() { - return new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) { + return new PlayQueueItemTouchCallback() { @Override - public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, - int viewSizeOutOfBounds, int totalSize, - long msSinceStartScroll) { - final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, - viewSizeOutOfBounds, totalSize, msSinceStartScroll); - final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, - Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY)); - return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, - RecyclerView.ViewHolder target) { - if (source.getItemViewType() != target.getItemViewType()) { - return false; - } - - final int sourceIndex = source.getLayoutPosition(); - final int targetIndex = target.getLayoutPosition(); + public void onMove(int sourceIndex, int targetIndex) { if (player != null) player.getPlayQueue().move(sourceIndex, targetIndex); - return true; } - - @Override - public boolean isLongPressDragEnabled() { - return false; - } - - @Override - public boolean isItemViewSwipeEnabled() { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {} }; } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index ea13a28e7..50c069b40 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -21,15 +21,15 @@ import org.schabi.newpipe.playlist.events.MoveEvent; import org.schabi.newpipe.playlist.events.PlayQueueEvent; import org.schabi.newpipe.playlist.events.RemoveEvent; import org.schabi.newpipe.playlist.events.ReorderEvent; +import org.schabi.newpipe.util.ServiceHelper; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import io.reactivex.Observable; import io.reactivex.Single; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; @@ -42,7 +42,7 @@ import io.reactivex.subjects.PublishSubject; import static org.schabi.newpipe.playlist.PlayQueue.DEBUG; public class MediaSourceManager { - @NonNull private final static String TAG = "MediaSourceManager"; + @NonNull private final String TAG = "MediaSourceManager@" + hashCode(); /** * Determines how many streams before and after the current stream should be loaded. @@ -60,17 +60,18 @@ public class MediaSourceManager { @NonNull private final PlayQueue playQueue; /** - * Determines how long NEIGHBOURING {@link LoadedMediaSource} window of a currently playing - * {@link MediaSource} is allowed to stay in the playlist timeline. This is to ensure - * the {@link StreamInfo} used in subsequent playback is up-to-date. - *

- * Once a {@link LoadedMediaSource} has expired, a new source will be reloaded to - * replace the expired one on whereupon {@link #loadImmediate()} is called. + * Determines the gap time between the playback position and the playback duration which + * the {@link #getEdgeIntervalSignal()} begins to request loading. * - * @see #loadImmediate() - * @see #isCorrectionNeeded(PlayQueueItem) + * @see #progressUpdateIntervalMillis * */ - private final long windowRefreshTimeMillis; + private final long playbackNearEndGapMillis; + /** + * Determines the interval which the {@link #getEdgeIntervalSignal()} waits for between + * each request for loading, once {@link #playbackNearEndGapMillis} has reached. + * */ + private final long progressUpdateIntervalMillis; + @NonNull private final Observable nearEndIntervalSignal; /** * Process only the last load order when receiving a stream of load orders (lessens I/O). @@ -106,23 +107,31 @@ public class MediaSourceManager { public MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue) { - this(listener, playQueue, - /*loadDebounceMillis=*/400L, - /*windowRefreshTimeMillis=*/TimeUnit.MILLISECONDS.convert(10, TimeUnit.MINUTES)); + this(listener, playQueue, /*loadDebounceMillis=*/400L, + /*playbackNearEndGapMillis=*/TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS), + /*progressUpdateIntervalMillis*/TimeUnit.MILLISECONDS.convert(2, TimeUnit.SECONDS)); } private MediaSourceManager(@NonNull final PlaybackListener listener, @NonNull final PlayQueue playQueue, final long loadDebounceMillis, - final long windowRefreshTimeMillis) { + final long playbackNearEndGapMillis, + final long progressUpdateIntervalMillis) { if (playQueue.getBroadcastReceiver() == null) { throw new IllegalArgumentException("Play Queue has not been initialized."); } + if (playbackNearEndGapMillis < progressUpdateIntervalMillis) { + throw new IllegalArgumentException("Playback end gap=[" + playbackNearEndGapMillis + + " ms] must be longer than update interval=[ " + progressUpdateIntervalMillis + + " ms] for them to be useful."); + } this.playbackListener = listener; this.playQueue = playQueue; - this.windowRefreshTimeMillis = windowRefreshTimeMillis; + this.playbackNearEndGapMillis = playbackNearEndGapMillis; + this.progressUpdateIntervalMillis = progressUpdateIntervalMillis; + this.nearEndIntervalSignal = getEdgeIntervalSignal(); this.loadDebounceMillis = loadDebounceMillis; this.debouncedSignal = PublishSubject.create(); @@ -161,28 +170,6 @@ public class MediaSourceManager { sources.releaseSource(); } - /** - * Loads the current playing stream and the streams within its windowSize bound. - * - * Unblocks the player once the item at the current index is loaded. - * */ - public void load() { - if (DEBUG) Log.d(TAG, "load() called."); - loadDebounced(); - } - - /** - * Blocks the player and repopulate the sources. - * - * Does not ensure the player is unblocked and should be done explicitly - * through {@link #load() load}. - * */ - public void reset() { - if (DEBUG) Log.d(TAG, "reset() called."); - - maybeBlock(); - populateSources(); - } /*////////////////////////////////////////////////////////////////////////// // Event Reactor //////////////////////////////////////////////////////////////////////////*/ @@ -219,11 +206,13 @@ public class MediaSourceManager { switch (event.type()) { case INIT: case ERROR: - reset(); - break; + maybeBlock(); case APPEND: populateSources(); break; + case SELECT: + maybeRenewCurrentIndex(); + break; case REMOVE: final RemoveEvent removeEvent = (RemoveEvent) event; remove(removeEvent.getRemoveIndex()); @@ -238,7 +227,6 @@ public class MediaSourceManager { final ReorderEvent reorderEvent = (ReorderEvent) event; move(reorderEvent.getFromSelectedIndex(), reorderEvent.getToSelectedIndex()); break; - case SELECT: case RECOVERY: default: break; @@ -347,8 +335,13 @@ public class MediaSourceManager { // MediaSource Loading //////////////////////////////////////////////////////////////////////////*/ + private Observable getEdgeIntervalSignal() { + return Observable.interval(progressUpdateIntervalMillis, TimeUnit.MILLISECONDS) + .filter(ignored -> playbackListener.isNearPlaybackEdge(playbackNearEndGapMillis)); + } + private Disposable getDebouncedLoader() { - return debouncedSignal + return debouncedSignal.mergeWith(nearEndIntervalSignal) .debounce(loadDebounceMillis, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(timestamp -> loadImmediate()); @@ -359,13 +352,14 @@ public class MediaSourceManager { } private void loadImmediate() { + if (DEBUG) Log.d(TAG, "MediaSource - loadImmediate() called"); // The current item has higher priority final int currentIndex = playQueue.getIndex(); final PlayQueueItem currentItem = playQueue.getItem(currentIndex); if (currentItem == null) return; // Evict the items being loaded to free up memory - if (!loadingItems.contains(currentItem) && loaderReactor.size() > MAXIMUM_LOADER_SIZE) { + if (loaderReactor.size() > MAXIMUM_LOADER_SIZE) { loaderReactor.clear(); loadingItems.clear(); } @@ -377,7 +371,7 @@ public class MediaSourceManager { final int leftBound = Math.max(0, currentIndex - WINDOW_SIZE); final int rightLimit = currentIndex + WINDOW_SIZE + 1; final int rightBound = Math.min(playQueue.size(), rightLimit); - final List items = new ArrayList<>( + final Set items = new HashSet<>( playQueue.getStreams().subList(leftBound,rightBound)); // Do a round robin @@ -385,6 +379,7 @@ public class MediaSourceManager { if (excess >= 0) { items.addAll(playQueue.getStreams().subList(0, Math.min(playQueue.size(), excess))); } + items.remove(currentItem); for (final PlayQueueItem item : items) { maybeLoadItem(item); @@ -405,9 +400,9 @@ public class MediaSourceManager { /* No exception handling since getLoadedMediaSource guarantees nonnull return */ .subscribe(mediaSource -> onMediaSourceReceived(item, mediaSource)); loaderReactor.add(loader); + } else { + maybeSynchronizePlayer(); } - - maybeSynchronizePlayer(); } private Single getLoadedMediaSource(@NonNull final PlayQueueItem stream) { @@ -423,7 +418,8 @@ public class MediaSourceManager { return new FailedMediaSource(stream, exception); } - final long expiration = System.currentTimeMillis() + windowRefreshTimeMillis; + final long expiration = System.currentTimeMillis() + + ServiceHelper.getCacheExpirationMillis(streamInfo.getServiceId()); return new LoadedMediaSource(source, stream, expiration); }).onErrorReturn(throwable -> new FailedMediaSource(stream, throwable)); } @@ -467,6 +463,24 @@ public class MediaSourceManager { } } + /** + * Checks if the current playing index contains an expired {@link ManagedMediaSource}. + * If so, the expired source is replaced by a {@link PlaceholderMediaSource} and + * {@link #loadImmediate()} is called to reload the current item. + * */ + private void maybeRenewCurrentIndex() { + final int currentIndex = playQueue.getIndex(); + if (sources.getSize() <= currentIndex) return; + + final ManagedMediaSource currentSource = + (ManagedMediaSource) sources.getMediaSource(currentIndex); + final PlayQueueItem currentItem = playQueue.getItem(); + if (!currentSource.canReplace(currentItem)) return; + + if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " + + "index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]"); + update(currentIndex, new PlaceholderMediaSource(), this::loadImmediate); + } /*////////////////////////////////////////////////////////////////////////// // MediaSource Playlist Helpers //////////////////////////////////////////////////////////////////////////*/ @@ -476,6 +490,7 @@ public class MediaSourceManager { this.sources.releaseSource(); this.sources = new DynamicConcatenatingMediaSource(false, + // Shuffling is done on PlayQueue, thus no need to use ExoPlayer's shuffle order new ShuffleOrder.UnshuffledShuffleOrder(0)); } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java index b37a269e2..34c7702bc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/PlaybackListener.java @@ -11,6 +11,16 @@ import org.schabi.newpipe.playlist.PlayQueueItem; import java.util.List; public interface PlaybackListener { + + /** + * Called to check if the currently playing stream is close to the end of its playback. + * Implementation should return true when the current playback position is within + * timeToEndMillis or less until its playback completes or transitions. + * + * May be called at any time. + * */ + boolean isNearPlaybackEdge(final long timeToEndMillis); + /** * Called when the stream at the current queue index is not ready yet. * Signals to the listener to block the player from playing anything and notify the source diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemTouchCallback.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemTouchCallback.java new file mode 100644 index 000000000..405dba11e --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemTouchCallback.java @@ -0,0 +1,52 @@ +package org.schabi.newpipe.playlist; + +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.helper.ItemTouchHelper; + +public abstract class PlayQueueItemTouchCallback extends ItemTouchHelper.SimpleCallback { + private static final int MINIMUM_INITIAL_DRAG_VELOCITY = 10; + private static final int MAXIMUM_INITIAL_DRAG_VELOCITY = 25; + + public PlayQueueItemTouchCallback() { + super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0); + } + + public abstract void onMove(final int sourceIndex, final int targetIndex); + + @Override + public int interpolateOutOfBoundsScroll(RecyclerView recyclerView, int viewSize, + int viewSizeOutOfBounds, int totalSize, + long msSinceStartScroll) { + final int standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, + viewSizeOutOfBounds, totalSize, msSinceStartScroll); + final int clampedAbsVelocity = Math.max(MINIMUM_INITIAL_DRAG_VELOCITY, + Math.min(Math.abs(standardSpeed), MAXIMUM_INITIAL_DRAG_VELOCITY)); + return clampedAbsVelocity * (int) Math.signum(viewSizeOutOfBounds); + } + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder source, + RecyclerView.ViewHolder target) { + if (source.getItemViewType() != target.getItemViewType()) { + return false; + } + + final int sourceIndex = source.getLayoutPosition(); + final int targetIndex = target.getLayoutPosition(); + onMove(sourceIndex, targetIndex); + return true; + } + + @Override + public boolean isLongPressDragEnabled() { + return false; + } + + @Override + public boolean isItemViewSwipeEnabled() { + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int swipeDir) {} +} diff --git a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java index e0836e06c..53e8d6fc4 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/HistorySettingsFragment.java @@ -1,12 +1,35 @@ package org.schabi.newpipe.settings; import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.preference.Preference; +import android.widget.Toast; import org.schabi.newpipe.R; +import org.schabi.newpipe.util.InfoCache; public class HistorySettingsFragment extends BasePreferenceFragment { + private String cacheWipeKey; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + cacheWipeKey = getString(R.string.metadata_cache_wipe_key); + } + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.history_settings); } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + if (preference.getKey().equals(cacheWipeKey)) { + InfoCache.getInstance().clearCache(); + Toast.makeText(preference.getContext(), R.string.metadata_cache_wipe_complete_notice, + Toast.LENGTH_SHORT).show(); + } + + return super.onPreferenceTreeClick(preference); + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java index 47c45e82a..ecc66bb40 100644 --- a/app/src/main/java/org/schabi/newpipe/util/InfoCache.java +++ b/app/src/main/java/org/schabi/newpipe/util/InfoCache.java @@ -43,7 +43,6 @@ public final class InfoCache { * Trim the cache to this size */ private static final int TRIM_CACHE_TO = 30; - private static final int DEFAULT_TIMEOUT_HOURS = 4; private static final LruCache lruCache = new LruCache<>(MAX_ITEMS_ON_CACHE); @@ -66,13 +65,7 @@ public final class InfoCache { public void putInfo(int serviceId, @NonNull String url, @NonNull Info info) { if (DEBUG) Log.d(TAG, "putInfo() called with: info = [" + info + "]"); - final long expirationMillis; - if (info.getServiceId() == SoundCloud.getServiceId()) { - expirationMillis = TimeUnit.MILLISECONDS.convert(15, TimeUnit.MINUTES); - } else { - expirationMillis = TimeUnit.MILLISECONDS.convert(DEFAULT_TIMEOUT_HOURS, TimeUnit.HOURS); - } - + final long expirationMillis = ServiceHelper.getCacheExpirationMillis(info.getServiceId()); synchronized (lruCache) { final CacheData data = new CacheData(info, expirationMillis); lruCache.put(keyOf(serviceId, url), data); diff --git a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java index 7d71750eb..9d71ae83a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ServiceHelper.java @@ -12,6 +12,10 @@ import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import java.util.concurrent.TimeUnit; + +import static org.schabi.newpipe.extractor.ServiceList.SoundCloud; + public class ServiceHelper { private static final StreamingService DEFAULT_FALLBACK_SERVICE = ServiceList.YouTube; @@ -98,4 +102,12 @@ public class ServiceHelper { PreferenceManager.getDefaultSharedPreferences(context).edit(). putString(context.getString(R.string.current_service_key), serviceName).apply(); } + + public static long getCacheExpirationMillis(final int serviceId) { + if (serviceId == SoundCloud.getServiceId()) { + return TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES); + } else { + return TimeUnit.MILLISECONDS.convert(1, TimeUnit.HOURS); + } + } } diff --git a/app/src/main/res/layout-land/activity_player_queue_control.xml b/app/src/main/res/layout-land/activity_player_queue_control.xml index c3480c547..11765f901 100644 --- a/app/src/main/res/layout-land/activity_player_queue_control.xml +++ b/app/src/main/res/layout-land/activity_player_queue_control.xml @@ -301,9 +301,13 @@ android:id="@+id/live_sync" android:layout_width="wrap_content" android:layout_height="match_parent" + android:paddingLeft="4dp" + android:paddingRight="4dp" android:gravity="center" - android:text="@string/live_sync" + android:text="@string/duration_live" + android:textAllCaps="true" android:textColor="?attr/colorAccent" + android:maxLength="4" android:background="?attr/selectableItemBackground" android:visibility="gone"/> diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index e7d337c17..8f608de3a 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -308,7 +308,7 @@ android:id="@+id/toggleOrientation" android:layout_width="30dp" android:layout_height="30dp" - android:layout_marginLeft="2dp" + android:layout_marginLeft="4dp" android:layout_marginRight="2dp" android:layout_alignParentRight="true" android:layout_centerVertical="true" @@ -325,8 +325,8 @@ android:id="@+id/switchPopup" android:layout_width="30dp" android:layout_height="30dp" - android:layout_marginLeft="2dp" - android:layout_marginRight="2dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" android:layout_toLeftOf="@id/toggleOrientation" android:layout_centerVertical="true" android:clickable="true" @@ -341,8 +341,8 @@ android:id="@+id/switchBackground" android:layout_width="30dp" android:layout_height="30dp" - android:layout_marginLeft="2dp" - android:layout_marginRight="2dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" android:layout_toLeftOf="@id/switchPopup" android:layout_centerVertical="true" android:clickable="true" @@ -403,9 +403,13 @@ android:id="@+id/playbackLiveSync" android:layout_width="wrap_content" android:layout_height="match_parent" + android:paddingLeft="4dp" + android:paddingRight="4dp" android:gravity="center" - android:text="@string/live_sync" + android:text="@string/duration_live" + android:textAllCaps="true" android:textColor="@android:color/white" + android:maxLength="4" android:visibility="gone" android:background="?attr/selectableItemBackground" tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry" /> diff --git a/app/src/main/res/layout/activity_player_queue_control.xml b/app/src/main/res/layout/activity_player_queue_control.xml index 639a8037c..7f649e382 100644 --- a/app/src/main/res/layout/activity_player_queue_control.xml +++ b/app/src/main/res/layout/activity_player_queue_control.xml @@ -151,9 +151,13 @@ android:id="@+id/live_sync" android:layout_width="wrap_content" android:layout_height="match_parent" + android:paddingLeft="4dp" + android:paddingRight="4dp" android:gravity="center" - android:text="@string/live_sync" + android:text="@string/duration_live" + android:textAllCaps="true" android:textColor="?attr/colorAccent" + android:maxLength="4" android:background="?attr/selectableItemBackground" android:visibility="gone"/> diff --git a/app/src/main/res/layout/player_popup.xml b/app/src/main/res/layout/player_popup.xml index 9bbd72fec..0c3ea77df 100644 --- a/app/src/main/res/layout/player_popup.xml +++ b/app/src/main/res/layout/player_popup.xml @@ -195,9 +195,13 @@ android:id="@+id/playbackLiveSync" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:paddingLeft="4dp" + android:paddingRight="4dp" android:gravity="center_vertical" - android:text="@string/live_sync" + android:text="@string/duration_live" + android:textAllCaps="true" android:textColor="@android:color/white" + android:maxLength="4" android:visibility="gone" android:background="?attr/selectableItemBackground" tools:ignore="HardcodedText,RtlHardcoded,RtlSymmetry" /> diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index a897aa185..68d75737a 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -160,6 +160,10 @@ import_data export_data + download_thumbnail_key + + cache_wipe_key + file_rename file_replacement_character diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c97f12809..e1a353807 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,6 +74,11 @@ Remember last size and position of popup Use fast inexact seek Inexact seek allows the player to seek to positions faster with reduced precision + Load thumbnails + Disable to stop all non-cached thumbnail from loading and save on data and memory usage + Wipe cached metadata + Remove all cached webpage data + Metadata cache wiped Auto-queue next stream Automatically append a related stream when playback starts on the last stream in a non-repeating play queue. Player gesture controls @@ -89,7 +94,7 @@ Download Next video Show next and similar videos - Show Hold to Append Tip + Show hold to append tip Show tip when background or popup button is pressed on video details page URL not supported Default content country @@ -98,7 +103,7 @@ Player Behavior Video & Audio - History + History & Cache Popup Appearance Other @@ -418,18 +423,16 @@ ZOOM Auto-generated - Caption Font Size - Smaller Font - Normal Font - Larger Font - - SYNC + Caption font size + Smaller font + Normal font + Larger font Enable LeakCanary Memory leak monitoring may cause app to become unresponsive when heap dumping - Report Out-of-Lifecycle Errors + Report Out-of-lifecycle errors Force reporting of undeliverable Rx exceptions occurring outside of fragment or activity lifecycle after dispose diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index c8c1efb12..2ce8bf9e6 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -37,6 +37,12 @@ android:summary="@string/auto_queue_summary" android:title="@string/auto_queue_title"/> + + + + From a5f9927459f48cdd4f57a648dd1247a85f978efb Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 13 Mar 2018 20:48:26 -0700 Subject: [PATCH 30/69] -Fixed main player animations not working on first call. --- app/src/main/res/layout/activity_main_player.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/activity_main_player.xml b/app/src/main/res/layout/activity_main_player.xml index 8f608de3a..c581c3203 100644 --- a/app/src/main/res/layout/activity_main_player.xml +++ b/app/src/main/res/layout/activity_main_player.xml @@ -52,7 +52,7 @@ android:id="@+id/playQueuePanel" android:layout_width="match_parent" android:layout_height="match_parent" - android:visibility="gone" + android:visibility="invisible" android:background="?attr/queue_background_color" tools:visibility="visible"> @@ -254,7 +254,7 @@ android:focusable="true" android:scaleType="fitXY" android:src="@drawable/ic_expand_more_white_24dp" - android:background="?attr/selectableItemBackground" + android:background="?attr/selectableItemBackgroundBorderless" tools:ignore="ContentDescription,RtlHardcoded"/> @@ -266,7 +266,7 @@ android:gravity="top" android:paddingLeft="5dp" android:paddingRight="5dp" - android:visibility="gone" + android:visibility="invisible" tools:ignore="RtlHardcoded" tools:visibility="visible"> From 2fa9aa04f4f6025b2b38306680a6b99d0ab999b4 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Tue, 13 Mar 2018 21:45:44 -0700 Subject: [PATCH 31/69] -Bump support library and multidex version. --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9fa911e54..3529a37b1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,7 @@ android { } ext { - supportLibVersion = '27.0.2' + supportLibVersion = '27.1.0' } dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') { @@ -77,7 +77,7 @@ dependencies { debugImplementation 'com.facebook.stetho:stetho:1.5.0' debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0' - debugImplementation 'com.android.support:multidex:1.0.2' + debugImplementation 'com.android.support:multidex:1.0.3' implementation 'io.reactivex.rxjava2:rxjava:2.1.7' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' From 0258726f0a4ffed04b7255389c104b3b93d6e31c Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 15 Mar 2018 20:07:20 -0700 Subject: [PATCH 32/69] -Changed thumbnail toggle in disabled mode to load dark dummy image. -Changed play queue items to display service names. -Fixed Soundcloud playlist not fitting thumbnail. -Refactored image display options to follow uniform behavior. -Refactoring and style changes on audio reactor and media button receiver. --- .../java/org/schabi/newpipe/BaseFragment.java | 33 --------- .../org/schabi/newpipe/ImageDownloader.java | 22 +++--- .../fragments/detail/VideoDetailFragment.java | 7 +- .../list/channel/ChannelFragment.java | 7 +- .../list/playlist/PlaylistFragment.java | 4 +- .../fragments/local/LocalItemBuilder.java | 2 - .../local/holder/LocalItemHolder.java | 21 ------ .../local/holder/LocalPlaylistItemHolder.java | 9 +-- .../holder/LocalPlaylistStreamItemHolder.java | 19 +---- .../LocalStatisticStreamItemHolder.java | 17 +---- .../local/holder/PlaylistItemHolder.java | 13 ---- .../holder/RemotePlaylistItemHolder.java | 3 +- .../newpipe/history/WatchHistoryFragment.java | 3 +- .../holder/ChannelMiniInfoItemHolder.java | 17 +---- .../info_list/holder/InfoItemHolder.java | 14 ---- .../holder/PlaylistMiniInfoItemHolder.java | 17 +---- .../holder/StreamMiniInfoItemHolder.java | 16 +---- .../newpipe/player/BackgroundPlayer.java | 70 +++++++++++-------- .../org/schabi/newpipe/player/BasePlayer.java | 10 +-- .../newpipe/player/helper/AudioReactor.java | 44 ++++++------ .../playlist/PlayQueueItemBuilder.java | 41 ++--------- .../settings/ContentSettingsFragment.java | 25 +++++++ .../newpipe/util/ImageDisplayConstants.java | 58 +++++++++++++++ .../main/res/layout/list_playlist_item.xml | 2 +- app/src/main/res/values/strings.xml | 3 +- 25 files changed, 206 insertions(+), 271 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java index 8967d8cb0..ce4318427 100644 --- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java @@ -8,9 +8,7 @@ import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; -import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.squareup.leakcanary.RefWatcher; import icepick.Icepick; @@ -94,35 +92,4 @@ public abstract class BaseFragment extends Fragment { activity.getSupportActionBar().setTitle(title); } } - - /*////////////////////////////////////////////////////////////////////////// - // DisplayImageOptions default configurations - //////////////////////////////////////////////////////////////////////////*/ - - public static final DisplayImageOptions BASE_OPTIONS = - new DisplayImageOptions.Builder().cacheInMemory(true).build(); - - public static final DisplayImageOptions DISPLAY_AVATAR_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_OPTIONS) - .showImageOnLoading(R.drawable.buddy) - .showImageForEmptyUri(R.drawable.buddy) - .showImageOnFail(R.drawable.buddy) - .build(); - - public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_OPTIONS) - .displayer(new FadeInBitmapDisplayer(250)) - .showImageForEmptyUri(R.drawable.dummy_thumbnail) - .showImageOnFail(R.drawable.dummy_thumbnail) - .build(); - - public static final DisplayImageOptions DISPLAY_BANNER_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_OPTIONS) - .showImageOnLoading(R.drawable.channel_banner) - .showImageForEmptyUri(R.drawable.channel_banner) - .showImageOnFail(R.drawable.channel_banner) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java index 8baabed6b..eb5e92e88 100644 --- a/app/src/main/java/org/schabi/newpipe/ImageDownloader.java +++ b/app/src/main/java/org/schabi/newpipe/ImageDownloader.java @@ -1,26 +1,26 @@ package org.schabi.newpipe; +import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; import android.preference.PreferenceManager; import com.nostra13.universalimageloader.core.download.BaseImageDownloader; import org.schabi.newpipe.extractor.NewPipe; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; public class ImageDownloader extends BaseImageDownloader { - private static final ByteArrayInputStream DUMMY_INPUT_STREAM = - new ByteArrayInputStream(new byte[]{}); - + private final Resources resources; private final SharedPreferences preferences; private final String downloadThumbnailKey; public ImageDownloader(Context context) { super(context); + this.resources = context.getResources(); this.preferences = PreferenceManager.getDefaultSharedPreferences(context); this.downloadThumbnailKey = context.getString(R.string.download_thumbnail_key); } @@ -29,12 +29,18 @@ public class ImageDownloader extends BaseImageDownloader { return preferences.getBoolean(downloadThumbnailKey, true); } - protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException { + @SuppressLint("ResourceType") + @Override + public InputStream getStream(String imageUri, Object extra) throws IOException { if (isDownloadingThumbnail()) { - final Downloader downloader = (Downloader) NewPipe.getDownloader(); - return downloader.stream(imageUri); + return super.getStream(imageUri, extra); } else { - return DUMMY_INPUT_STREAM; + return resources.openRawResource(R.drawable.dummy_thumbnail_dark); } } + + protected InputStream getStreamFromNetwork(String imageUri, Object extra) throws IOException { + final Downloader downloader = (Downloader) NewPipe.getDownloader(); + return downloader.stream(imageUri); + } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index b3ca5f47f..2a95125df 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -73,6 +73,7 @@ import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.InfoCache; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; @@ -587,7 +588,8 @@ public class VideoDetailFragment imageLoader.displayImage( info.getThumbnailUrl(), thumbnailImageView, - DISPLAY_THUMBNAIL_OPTIONS, new SimpleImageLoadingListener() { + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, + new SimpleImageLoadingListener() { @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { ErrorActivity.reportError( @@ -604,7 +606,8 @@ public class VideoDetailFragment } if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) { - imageLoader.displayImage(info.getUploaderAvatarUrl(), uploaderThumb, DISPLAY_AVATAR_OPTIONS); + imageLoader.displayImage(info.getUploaderAvatarUrl(), uploaderThumb, + ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index 7783d8a98..dbc61961e 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -44,6 +44,7 @@ import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.subscription.SubscriptionService; import org.schabi.newpipe.util.AnimationUtils; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -419,8 +420,10 @@ public class ChannelFragment extends BaseListInfoFragment { super.handleResult(result); headerRootLayout.setVisibility(View.VISIBLE); - imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner, DISPLAY_BANNER_OPTIONS); - imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView, DISPLAY_AVATAR_OPTIONS); + imageLoader.displayImage(result.getBannerUrl(), headerChannelBanner, + ImageDisplayConstants.DISPLAY_BANNER_OPTIONS); + imageLoader.displayImage(result.getAvatarUrl(), headerAvatarView, + ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); if (result.getSubscriberCount() != -1) { headerSubscribersTextView.setText(Localization.localizeSubscribersCount(activity, result.getSubscriberCount())); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java index 9033560bd..3bcf9d322 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java @@ -37,6 +37,7 @@ import org.schabi.newpipe.playlist.PlaylistPlayQueue; import org.schabi.newpipe.playlist.SinglePlayQueue; import org.schabi.newpipe.report.UserAction; import org.schabi.newpipe.util.ExtractorHelper; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.ThemeHelper; @@ -271,7 +272,8 @@ public class PlaylistFragment extends BaseListInfoFragment { playlistCtrl.setVisibility(View.VISIBLE); - imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, DISPLAY_AVATAR_OPTIONS); + imageLoader.displayImage(result.getUploaderAvatarUrl(), headerUploaderAvatar, + ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS); headerStreamCount.setText(getResources().getQuantityString(R.plurals.videos, (int) result.getStreamCount(), (int) result.getStreamCount())); diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemBuilder.java b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemBuilder.java index 4794def97..5dc6c17a4 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/LocalItemBuilder.java @@ -1,12 +1,10 @@ package org.schabi.newpipe.fragments.local; import android.content.Context; -import android.graphics.Bitmap; import android.widget.ImageView; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.process.BitmapProcessor; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.util.OnClickGesture; diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalItemHolder.java b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalItemHolder.java index e4087d8a8..2dffdbfdb 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalItemHolder.java @@ -1,14 +1,8 @@ package org.schabi.newpipe.fragments.local.holder; -import android.graphics.Bitmap; -import android.support.annotation.DimenRes; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.ViewGroup; -import android.widget.ImageView; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.process.BitmapProcessor; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.fragments.local.LocalItemBuilder; @@ -45,19 +39,4 @@ public abstract class LocalItemHolder extends RecyclerView.ViewHolder { } public abstract void updateFromItem(final LocalItem item, final DateFormat dateFormat); - - /*////////////////////////////////////////////////////////////////////////// - // ImageLoaderOptions - //////////////////////////////////////////////////////////////////////////*/ - - /** - * Base display options - */ - public static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS = - new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .bitmapConfig(Bitmap.Config.RGB_565) - .resetViewBeforeLoading(false) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistItemHolder.java index 1fbea6cc4..d9eb7caa5 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistItemHolder.java @@ -2,15 +2,11 @@ package org.schabi.newpipe.fragments.local.holder; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - -import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistMetadataEntry; import org.schabi.newpipe.fragments.local.LocalItemBuilder; +import org.schabi.newpipe.util.ImageDisplayConstants; import java.text.DateFormat; @@ -29,7 +25,8 @@ public class LocalPlaylistItemHolder extends PlaylistItemHolder { itemStreamCountView.setText(String.valueOf(item.streamCount)); itemUploaderView.setVisibility(View.INVISIBLE); - itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, DISPLAY_THUMBNAIL_OPTIONS); + itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, + ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); super.updateFromItem(localItem, dateFormat); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistStreamItemHolder.java index 0696f5f61..5f9555d9f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalPlaylistStreamItemHolder.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.fragments.local.holder; -import android.graphics.Bitmap; import android.support.v4.content.ContextCompat; import android.view.MotionEvent; import android.view.View; @@ -8,14 +7,12 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; - import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.PlaylistStreamEntry; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.fragments.local.LocalItemBuilder; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import java.text.DateFormat; @@ -61,7 +58,8 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { } // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, DISPLAY_THUMBNAIL_OPTIONS); + itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { @@ -92,15 +90,4 @@ public class LocalPlaylistStreamItemHolder extends LocalItemHolder { return false; }; } - - /** - * Display options for stream thumbnails - */ - private static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageOnFail(R.drawable.dummy_thumbnail) - .showImageForEmptyUri(R.drawable.dummy_thumbnail) - .showImageOnLoading(R.drawable.dummy_thumbnail) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalStatisticStreamItemHolder.java b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalStatisticStreamItemHolder.java index cd0630b37..199158672 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalStatisticStreamItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/LocalStatisticStreamItemHolder.java @@ -6,13 +6,12 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.stream.StreamStatisticsEntry; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.fragments.local.LocalItemBuilder; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import java.text.DateFormat; @@ -84,7 +83,8 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { itemAdditionalDetails.setText(getStreamInfoDetailLine(item, dateFormat)); // Default thumbnail is shown on error, while loading and if the url is empty - itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, DISPLAY_THUMBNAIL_OPTIONS); + itemBuilder.displayImage(item.thumbnailUrl, itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnItemSelectedListener() != null) { @@ -100,15 +100,4 @@ public class LocalStatisticStreamItemHolder extends LocalItemHolder { return true; }); } - - /** - * Display options for stream thumbnails - */ - public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageOnFail(R.drawable.dummy_thumbnail) - .showImageForEmptyUri(R.drawable.dummy_thumbnail) - .showImageOnLoading(R.drawable.dummy_thumbnail) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/PlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/PlaylistItemHolder.java index bab76ddcb..57bc2a3cb 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/PlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/PlaylistItemHolder.java @@ -4,8 +4,6 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - import org.schabi.newpipe.R; import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.fragments.local.LocalItemBuilder; @@ -48,15 +46,4 @@ public abstract class PlaylistItemHolder extends LocalItemHolder { return true; }); } - - /** - * Display options for playlist thumbnails - */ - public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageOnLoading(R.drawable.dummy_thumbnail_playlist) - .showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist) - .showImageOnFail(R.drawable.dummy_thumbnail_playlist) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/RemotePlaylistItemHolder.java b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/RemotePlaylistItemHolder.java index 0f7b00e6d..871138464 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/holder/RemotePlaylistItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/holder/RemotePlaylistItemHolder.java @@ -6,6 +6,7 @@ import org.schabi.newpipe.database.LocalItem; import org.schabi.newpipe.database.playlist.model.PlaylistRemoteEntity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.fragments.local.LocalItemBuilder; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import java.text.DateFormat; @@ -26,7 +27,7 @@ public class RemotePlaylistItemHolder extends PlaylistItemHolder { NewPipe.getNameOfService(item.getServiceId()))); itemBuilder.displayImage(item.getThumbnailUrl(), itemThumbnailView, - DISPLAY_THUMBNAIL_OPTIONS); + ImageDisplayConstants.DISPLAY_PLAYLIST_OPTIONS); super.updateFromItem(localItem, dateFormat); } diff --git a/app/src/main/java/org/schabi/newpipe/history/WatchHistoryFragment.java b/app/src/main/java/org/schabi/newpipe/history/WatchHistoryFragment.java index 4830ed33b..4fe2b701d 100644 --- a/app/src/main/java/org/schabi/newpipe/history/WatchHistoryFragment.java +++ b/app/src/main/java/org/schabi/newpipe/history/WatchHistoryFragment.java @@ -20,6 +20,7 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; import org.schabi.newpipe.database.history.model.StreamHistoryEntry; import org.schabi.newpipe.info_list.holder.StreamInfoItemHolder; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -147,7 +148,7 @@ public class WatchHistoryFragment extends HistoryFragment { holder.uploader.setText(entry.uploader); holder.duration.setText(Localization.getDurationString(entry.duration)); ImageLoader.getInstance().displayImage(entry.thumbnailUrl, holder.thumbnailView, - StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); } } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java index 211fa60cd..643886da8 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/ChannelMiniInfoItemHolder.java @@ -1,15 +1,13 @@ package org.schabi.newpipe.info_list.holder; -import android.view.View; import android.view.ViewGroup; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.channel.ChannelInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; import de.hdodenhof.circleimageview.CircleImageView; @@ -42,7 +40,7 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { itemBuilder.getImageLoader() .displayImage(item.getThumbnailUrl(), itemThumbnailView, - ChannelInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnChannelSelectedListener() != null) { @@ -59,15 +57,4 @@ public class ChannelMiniInfoItemHolder extends InfoItemHolder { } return details; } - - /** - * Display options for channel thumbnails - */ - public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageOnLoading(R.drawable.buddy_channel_item) - .showImageForEmptyUri(R.drawable.buddy_channel_item) - .showImageOnFail(R.drawable.buddy_channel_item) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java index fb5aa2b7c..ebb5b4114 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/InfoItemHolder.java @@ -4,8 +4,6 @@ import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.ViewGroup; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; @@ -38,16 +36,4 @@ public abstract class InfoItemHolder extends RecyclerView.ViewHolder { } public abstract void updateFromItem(final InfoItem infoItem); - - /*////////////////////////////////////////////////////////////////////////// - // ImageLoaderOptions - //////////////////////////////////////////////////////////////////////////*/ - - /** - * Base display options - */ - public static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS = - new DisplayImageOptions.Builder() - .cacheInMemory(true) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java index 30d84e1bd..b6bd2f389 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/PlaylistMiniInfoItemHolder.java @@ -4,12 +4,11 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.util.ImageDisplayConstants; public class PlaylistMiniInfoItemHolder extends InfoItemHolder { public final ImageView itemThumbnailView; @@ -40,7 +39,8 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder { itemUploaderView.setText(item.getUploaderName()); itemBuilder.getImageLoader() - .displayImage(item.getThumbnailUrl(), itemThumbnailView, DISPLAY_THUMBNAIL_OPTIONS); + .displayImage(item.getThumbnailUrl(), itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnPlaylistSelectedListener() != null) { @@ -56,15 +56,4 @@ public class PlaylistMiniInfoItemHolder extends InfoItemHolder { return true; }); } - - /** - * Display options for playlist thumbnails - */ - public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageOnLoading(R.drawable.dummy_thumbnail_playlist) - .showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist) - .showImageOnFail(R.drawable.dummy_thumbnail_playlist) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java index 72c2830e1..048b907af 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/StreamMiniInfoItemHolder.java @@ -6,13 +6,12 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.stream.StreamInfoItem; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.info_list.InfoItemBuilder; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; public class StreamMiniInfoItemHolder extends InfoItemHolder { @@ -61,7 +60,7 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { itemBuilder.getImageLoader() .displayImage(item.getThumbnailUrl(), itemThumbnailView, - StreamInfoItemHolder.DISPLAY_THUMBNAIL_OPTIONS); + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); itemView.setOnClickListener(view -> { if (itemBuilder.getOnStreamSelectedListener() != null) { @@ -98,15 +97,4 @@ public class StreamMiniInfoItemHolder extends InfoItemHolder { itemView.setLongClickable(false); itemView.setOnLongClickListener(null); } - - /** - * Display options for stream thumbnails - */ - public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = - new DisplayImageOptions.Builder() - .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) - .showImageOnFail(R.drawable.dummy_thumbnail) - .showImageForEmptyUri(R.drawable.dummy_thumbnail) - .showImageOnLoading(R.drawable.dummy_thumbnail) - .build(); } diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index 61720c6b4..83ed54cf5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -121,7 +121,7 @@ public final class BackgroundPlayer extends Service { shouldUpdateOnProgress = true; mReceiverComponent = new ComponentName(this, MediaButtonReceiver.class); - basePlayerImpl.audioReactor.registerMediaButtonEventReceiver(mReceiverComponent); + basePlayerImpl.getAudioReactor().registerMediaButtonEventReceiver(mReceiverComponent); } @Override @@ -152,7 +152,7 @@ public final class BackgroundPlayer extends Service { lockManager.releaseWifiAndCpu(); } if (basePlayerImpl != null) { - basePlayerImpl.audioReactor.unregisterMediaButtonEventReceiver(mReceiverComponent); + basePlayerImpl.getAudioReactor().unregisterMediaButtonEventReceiver(mReceiverComponent); basePlayerImpl.stopActivityBinding(); basePlayerImpl.destroy(); } @@ -575,38 +575,46 @@ public final class BackgroundPlayer extends Service { } public static class MediaButtonReceiver extends BroadcastReceiver { - - public MediaButtonReceiver() { - super(); - } - @Override public void onReceive(Context context, Intent intent) { - if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { - KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); - if (event.getAction() == KeyEvent.ACTION_UP) { - int keycode = event.getKeyCode(); - PendingIntent pendingIntent = null; - if (keycode == KeyEvent.KEYCODE_MEDIA_NEXT) { - pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT); - } else if (keycode == KeyEvent.KEYCODE_MEDIA_PREVIOUS) { - pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT); - } else if (keycode == KeyEvent.KEYCODE_HEADSETHOOK || keycode == KeyEvent.KEYCODE_MEDIA_PAUSE || keycode == KeyEvent.KEYCODE_MEDIA_PLAY) { - pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT); - } else if (keycode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { - pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT); - } else if (keycode == KeyEvent.KEYCODE_MEDIA_REWIND) { - pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT); - } - if (pendingIntent != null) { - try { - pendingIntent.send(); - } catch (Exception e) { - Log.e(TAG, "Error Sending intent MediaButtonReceiver", e); - } - } + if (!Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) return; + final KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT); + if (event.getAction() != KeyEvent.ACTION_UP) return; + final int keycode = event.getKeyCode(); - } + final PendingIntent pendingIntent; + switch (keycode) { + case KeyEvent.KEYCODE_MEDIA_NEXT: + pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, + new Intent(ACTION_PLAY_NEXT), PendingIntent.FLAG_UPDATE_CURRENT); + break; + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, + new Intent(ACTION_PLAY_PREVIOUS), PendingIntent.FLAG_UPDATE_CURRENT); + break; + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY: + pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, + new Intent(ACTION_PLAY_PAUSE), PendingIntent.FLAG_UPDATE_CURRENT); + break; + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, + new Intent(ACTION_FAST_FORWARD), PendingIntent.FLAG_UPDATE_CURRENT); + break; + case KeyEvent.KEYCODE_MEDIA_REWIND: + pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, + new Intent(ACTION_FAST_REWIND), PendingIntent.FLAG_UPDATE_CURRENT); + break; + default: + pendingIntent = null; + } + + if (pendingIntent == null) return; + try { + pendingIntent.send(); + } catch (Exception e) { + Log.e(TAG, "Error Sending intent MediaButtonReceiver", e); } } } diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 5355e19ee..5ec61b058 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -838,9 +838,9 @@ public abstract class BasePlayer implements "queue index=[" + playQueue.getIndex() + "]"); // Check if bad seek position - } else if ((currentPlaylistSize > 0 && currentPlayQueueIndex > currentPlaylistSize) || - currentPlaylistIndex < 0) { - Log.e(TAG, "Playback - Trying to seek to " + + } else if ((currentPlaylistSize > 0 && currentPlayQueueIndex >= currentPlaylistSize) || + currentPlayQueueIndex < 0) { + Log.e(TAG, "Playback - Trying to seek to invalid " + "index=[" + currentPlayQueueIndex + "] with " + "playlist length=[" + currentPlaylistSize + "]"); @@ -848,9 +848,9 @@ public abstract class BasePlayer implements } else if (currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) { final long startPos = info != null ? info.getStartPosition() : C.TIME_UNSET; if (DEBUG) Log.d(TAG, "Playback - Rewinding to correct" + - " window=[" + currentPlayQueueIndex + "]," + + " index=[" + currentPlayQueueIndex + "]," + " at=[" + getTimeString((int)startPos) + "]," + - " from=[" + simpleExoPlayer.getCurrentWindowIndex() + "]."); + " from=[" + currentPlaylistIndex + "], size=[" + currentPlaylistSize + "]."); simpleExoPlayer.seekTo(currentPlayQueueIndex, startPos); } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java index df30c3e79..c1896599f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/AudioReactor.java @@ -22,6 +22,14 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au private static final String TAG = "AudioFocusReactor"; + private static final boolean SHOULD_BUILD_FOCUS_REQUEST = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; + + private static final boolean CAN_USE_MEDIA_BUTTONS = + Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1; + private static final String MEDIA_BUTTON_DEPRECATED_ERROR = + "registerMediaButtonEventReceiver has been deprecated and maybe not supported anymore."; + private static final int DUCK_DURATION = 1500; private static final float DUCK_AUDIO_TO = .2f; @@ -38,9 +46,9 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au this.player = player; this.context = context; this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); - player.setAudioDebugListener(this); + player.addAudioDebugListener(this); - if (shouldBuildFocusRequest()) { + if (SHOULD_BUILD_FOCUS_REQUEST) { request = new AudioFocusRequest.Builder(FOCUS_GAIN_TYPE) .setAcceptsDelayedFocusGain(true) .setWillPauseWhenDucked(true) @@ -56,7 +64,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au //////////////////////////////////////////////////////////////////////////*/ public void requestAudioFocus() { - if (shouldBuildFocusRequest()) { + if (SHOULD_BUILD_FOCUS_REQUEST) { audioManager.requestAudioFocus(request); } else { audioManager.requestAudioFocus(this, STREAM_TYPE, FOCUS_GAIN_TYPE); @@ -64,7 +72,7 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au } public void abandonAudioFocus() { - if (shouldBuildFocusRequest()) { + if (SHOULD_BUILD_FOCUS_REQUEST) { audioManager.abandonAudioFocusRequest(request); } else { audioManager.abandonAudioFocus(this); @@ -83,24 +91,20 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au audioManager.setStreamVolume(STREAM_TYPE, volume, 0); } - private boolean shouldBuildFocusRequest() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; - } - public void registerMediaButtonEventReceiver(ComponentName componentName) { - if (android.os.Build.VERSION.SDK_INT > 27) { - Log.e(TAG, "registerMediaButtonEventReceiver has been deprecated and maybe not supported anymore."); - return; + if (CAN_USE_MEDIA_BUTTONS) { + audioManager.registerMediaButtonEventReceiver(componentName); + } else { + Log.e(TAG, MEDIA_BUTTON_DEPRECATED_ERROR); } - audioManager.registerMediaButtonEventReceiver(componentName); } public void unregisterMediaButtonEventReceiver(ComponentName componentName) { - if (android.os.Build.VERSION.SDK_INT > 27) { - Log.e(TAG, "unregisterMediaButtonEventReceiver has been deprecated and maybe not supported anymore."); - return; + if (CAN_USE_MEDIA_BUTTONS) { + audioManager.unregisterMediaButtonEventReceiver(componentName); + } else { + Log.e(TAG, MEDIA_BUTTON_DEPRECATED_ERROR); } - audioManager.unregisterMediaButtonEventReceiver(componentName); } /*////////////////////////////////////////////////////////////////////////// @@ -165,12 +169,8 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au player.setVolume(to); } }); - valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - player.setVolume(((float) animation.getAnimatedValue())); - } - }); + valueAnimator.addUpdateListener(animation -> + player.setVolume(((float) animation.getAnimatedValue()))); valueAnimator.start(); } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java index 73cdf1113..7042bea89 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItemBuilder.java @@ -1,28 +1,22 @@ package org.schabi.newpipe.playlist; import android.content.Context; -import android.graphics.Bitmap; import android.text.TextUtils; import android.view.MotionEvent; import android.view.View; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; -import com.nostra13.universalimageloader.core.process.BitmapProcessor; import org.schabi.newpipe.R; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.Localization; - public class PlayQueueItemBuilder { private static final String TAG = PlayQueueItemBuilder.class.toString(); - private final int thumbnailWidthPx; - private final int thumbnailHeightPx; - private final DisplayImageOptions imageOptions; - public interface OnSelectedListener { void selected(PlayQueueItem item, View view); void held(PlayQueueItem item, View view); @@ -31,11 +25,7 @@ public class PlayQueueItemBuilder { private OnSelectedListener onItemClickListener; - public PlayQueueItemBuilder(final Context context) { - thumbnailWidthPx = context.getResources().getDimensionPixelSize(R.dimen.play_queue_thumbnail_width); - thumbnailHeightPx = context.getResources().getDimensionPixelSize(R.dimen.play_queue_thumbnail_height); - imageOptions = buildImageOptions(thumbnailWidthPx, thumbnailHeightPx); - } + public PlayQueueItemBuilder(final Context context) {} public void setOnSelectedListener(OnSelectedListener listener) { this.onItemClickListener = listener; @@ -43,7 +33,8 @@ public class PlayQueueItemBuilder { public void buildStreamInfoItem(final PlayQueueItemHolder holder, final PlayQueueItem item) { if (!TextUtils.isEmpty(item.getTitle())) holder.itemVideoTitleView.setText(item.getTitle()); - if (!TextUtils.isEmpty(item.getUploader())) holder.itemAdditionalDetailsView.setText(item.getUploader()); + holder.itemAdditionalDetailsView.setText(Localization.concatenateStrings(item.getUploader(), + NewPipe.getNameOfService(item.getServiceId()))); if (item.getDuration() > 0) { holder.itemDurationView.setText(Localization.getDurationString(item.getDuration())); @@ -51,7 +42,8 @@ public class PlayQueueItemBuilder { holder.itemDurationView.setVisibility(View.GONE); } - ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, imageOptions); + ImageLoader.getInstance().displayImage(item.getThumbnailUrl(), holder.itemThumbnailView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS); holder.itemRoot.setOnClickListener(view -> { if (onItemClickListener != null) { @@ -81,23 +73,4 @@ public class PlayQueueItemBuilder { return false; }; } - - private DisplayImageOptions buildImageOptions(final int widthPx, final int heightPx) { - final BitmapProcessor bitmapProcessor = bitmap -> { - final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, widthPx, heightPx, false); - bitmap.recycle(); - return resizedBitmap; - }; - - return new DisplayImageOptions.Builder() - .showImageOnFail(R.drawable.dummy_thumbnail) - .showImageForEmptyUri(R.drawable.dummy_thumbnail) - .showImageOnLoading(R.drawable.dummy_thumbnail) - .bitmapConfig(Bitmap.Config.RGB_565) // Users won't be able to see much anyways - .preProcessor(bitmapProcessor) - .imageScaleType(ImageScaleType.EXACTLY) - .cacheInMemory(true) - .cacheOnDisk(true) - .build(); - } } diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java index 26278ac75..f0ab3bc03 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java @@ -6,12 +6,14 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.util.Log; import android.widget.Toast; import com.nononsenseapps.filepicker.Utils; +import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.NewPipe; @@ -47,6 +49,29 @@ public class ContentSettingsFragment extends BasePreferenceFragment { private File newpipe_db; private File newpipe_db_journal; + private String thumbnailLoadToggleKey; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + thumbnailLoadToggleKey = getString(R.string.download_thumbnail_key); + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + if (preference.getKey().equals(thumbnailLoadToggleKey)) { + final ImageLoader imageLoader = ImageLoader.getInstance(); + imageLoader.stop(); + imageLoader.clearDiskCache(); + imageLoader.clearMemoryCache(); + imageLoader.resume(); + Toast.makeText(preference.getContext(), R.string.thumbnail_cache_wipe_complete_notice, + Toast.LENGTH_SHORT).show(); + } + + return super.onPreferenceTreeClick(preference); + } + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { diff --git a/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java b/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java new file mode 100644 index 000000000..9ee8a1095 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/ImageDisplayConstants.java @@ -0,0 +1,58 @@ +package org.schabi.newpipe.util; + +import android.graphics.Bitmap; + +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.assist.ImageScaleType; +import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; + +import org.schabi.newpipe.R; + +public class ImageDisplayConstants { + private static final int BITMAP_FADE_IN_DURATION_MILLIS = 250; + + /** + * Base display options + */ + private static final DisplayImageOptions BASE_DISPLAY_IMAGE_OPTIONS = + new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(true) + .resetViewBeforeLoading(true) + .bitmapConfig(Bitmap.Config.RGB_565) + .imageScaleType(ImageScaleType.EXACTLY) + .displayer(new FadeInBitmapDisplayer(BITMAP_FADE_IN_DURATION_MILLIS)) + .build(); + + /*////////////////////////////////////////////////////////////////////////// + // DisplayImageOptions default configurations + //////////////////////////////////////////////////////////////////////////*/ + + public static final DisplayImageOptions DISPLAY_AVATAR_OPTIONS = + new DisplayImageOptions.Builder() + .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) + .showImageForEmptyUri(R.drawable.buddy) + .showImageOnFail(R.drawable.buddy) + .build(); + + public static final DisplayImageOptions DISPLAY_THUMBNAIL_OPTIONS = + new DisplayImageOptions.Builder() + .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) + .showImageForEmptyUri(R.drawable.dummy_thumbnail) + .showImageOnFail(R.drawable.dummy_thumbnail) + .build(); + + public static final DisplayImageOptions DISPLAY_BANNER_OPTIONS = + new DisplayImageOptions.Builder() + .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) + .showImageForEmptyUri(R.drawable.channel_banner) + .showImageOnFail(R.drawable.channel_banner) + .build(); + + public static final DisplayImageOptions DISPLAY_PLAYLIST_OPTIONS = + new DisplayImageOptions.Builder() + .cloneFrom(BASE_DISPLAY_IMAGE_OPTIONS) + .showImageForEmptyUri(R.drawable.dummy_thumbnail_playlist) + .showImageOnFail(R.drawable.dummy_thumbnail_playlist) + .build(); +} diff --git a/app/src/main/res/layout/list_playlist_item.xml b/app/src/main/res/layout/list_playlist_item.xml index 23f5224c5..57a3cbef9 100644 --- a/app/src/main/res/layout/list_playlist_item.xml +++ b/app/src/main/res/layout/list_playlist_item.xml @@ -19,7 +19,7 @@ android:layout_alignParentTop="true" android:layout_marginRight="@dimen/video_item_search_image_right_margin" android:contentDescription="@string/list_thumbnail_view_description" - android:scaleType="fitEnd" + android:scaleType="centerCrop" android:src="@drawable/dummy_thumbnail_playlist" tools:ignore="RtlHardcoded"/> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e1a353807..cd280ff02 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,7 +75,8 @@ Use fast inexact seek Inexact seek allows the player to seek to positions faster with reduced precision Load thumbnails - Disable to stop all non-cached thumbnail from loading and save on data and memory usage + Disable to stop all thumbnails from loading and save on data and memory usage. Changing this will clear both in-memory and on-disk image cache. + Image cache wiped Wipe cached metadata Remove all cached webpage data Metadata cache wiped From 5a05cb96beeb73c7586af1f2c9c249b6213c5a18 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 15 Mar 2018 20:07:38 -0700 Subject: [PATCH 33/69] -Changed start position seek to occur after media source window has been prepared. -Fixed livestream not seeking to live when started from play queue. -Fixed media source manager synchronization to only occur after timeline change has completed. -Fixed auto queue not working when last item is replayed after the auto-queued item is removed. -Updated ExoPlayer to 2.7.1. --- app/build.gradle | 2 +- .../org/schabi/newpipe/player/BasePlayer.java | 139 +++++++++--------- .../newpipe/player/MainVideoPlayer.java | 1 + .../newpipe/player/PopupVideoPlayer.java | 2 +- .../newpipe/player/ServicePlayerActivity.java | 2 +- .../schabi/newpipe/player/VideoPlayer.java | 10 +- .../player/playback/MediaSourceManager.java | 15 +- 7 files changed, 92 insertions(+), 79 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3529a37b1..952bc3067 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,7 +73,7 @@ dependencies { implementation 'de.hdodenhof:circleimageview:2.2.0' implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1' implementation 'com.nononsenseapps:filepicker:4.2.1' - implementation 'com.google.android.exoplayer:exoplayer:2.7.0' + implementation 'com.google.android.exoplayer:exoplayer:2.7.1' debugImplementation 'com.facebook.stetho:stetho:1.5.0' debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0' diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index 5ec61b058..de85a3704 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -149,7 +149,8 @@ public abstract class BasePlayer implements protected SimpleExoPlayer simpleExoPlayer; protected AudioReactor audioReactor; - protected boolean isPrepared = false; + private boolean isPrepared = false; + private boolean isSynchronizing = false; protected Disposable progressUpdateReactor; protected CompositeDisposable databaseUpdateReactor; @@ -402,6 +403,7 @@ public abstract class BasePlayer implements // States Implementation //////////////////////////////////////////////////////////////////////////*/ + public static final int STATE_PREFLIGHT = -1; public static final int STATE_BLOCKED = 123; public static final int STATE_PLAYING = 124; public static final int STATE_BUFFERING = 125; @@ -409,7 +411,7 @@ public abstract class BasePlayer implements public static final int STATE_PAUSED_SEEK = 127; public static final int STATE_COMPLETED = 128; - protected int currentState = -1; + protected int currentState = STATE_PREFLIGHT; public void changeState(int state) { if (DEBUG) Log.d(TAG, "changeState() called with: state = [" + state + "]"); @@ -540,11 +542,13 @@ public abstract class BasePlayer implements case Player.TIMELINE_CHANGE_REASON_RESET: // called after #block case Player.TIMELINE_CHANGE_REASON_PREPARED: // called after #unblock case Player.TIMELINE_CHANGE_REASON_DYNAMIC: // called after playlist changes - // ensures MediaSourceManager#update is complete + // Ensures MediaSourceManager#update is complete final boolean isPlaylistStable = timeline.getWindowCount() == playQueue.size(); // Ensure dynamic/livestream timeline changes does not cause negative position - if (isPlaylistStable && !isCurrentWindowValid()) { - simpleExoPlayer.seekTo(/*clampToMillis=*/0); + if (isPlaylistStable && !isCurrentWindowValid() && !isSynchronizing) { + if (DEBUG) Log.d(TAG, "Playback - negative time position reached, " + + "clamping position to default time."); + seekTo(/*clampToTime=*/0); } break; } @@ -596,49 +600,55 @@ public abstract class BasePlayer implements } break; case Player.STATE_READY: //3 - maybeRecover(); + maybeCorrectSeekPosition(); if (!isPrepared) { isPrepared = true; onPrepared(playWhenReady); break; } - if (currentState == STATE_PAUSED_SEEK) break; changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); break; case Player.STATE_ENDED: // 4 - // Ensure the current window has actually ended - // since single windows that are still loading may produce an ended state - if (isCurrentWindowValid() && - simpleExoPlayer.getCurrentPosition() >= simpleExoPlayer.getDuration()) { - changeState(STATE_COMPLETED); - isPrepared = false; - } + changeState(STATE_COMPLETED); + isPrepared = false; break; } } - private void maybeRecover() { + private void maybeCorrectSeekPosition() { + if (playQueue == null || simpleExoPlayer == null || currentInfo == null) return; + final int currentSourceIndex = playQueue.getIndex(); final PlayQueueItem currentSourceItem = playQueue.getItem(); + if (currentSourceItem == null) return; - // Check if already playing correct window - final boolean isCurrentPeriodCorrect = + final long recoveryPositionMillis = currentSourceItem.getRecoveryPosition(); + final boolean isCurrentWindowCorrect = simpleExoPlayer.getCurrentPeriodIndex() == currentSourceIndex; + final long presetStartPositionMillis = currentInfo.getStartPosition() * 1000; - // Check if recovering - if (isCurrentPeriodCorrect && currentSourceItem != null) { - /* Recovering with sub-second position may cause a long buffer delay in ExoPlayer, - * rounding this position to the nearest second will help alleviate this.*/ - final long position = currentSourceItem.getRecoveryPosition(); - - /* Skip recovering if the recovery position is not set.*/ - if (position == PlayQueueItem.RECOVERY_UNSET) return; - - if (DEBUG) Log.d(TAG, "Rewinding to recovery window: " + currentSourceIndex + - " at: " + getTimeString((int)position)); - simpleExoPlayer.seekTo(currentSourceItem.getRecoveryPosition()); + if (recoveryPositionMillis != PlayQueueItem.RECOVERY_UNSET && isCurrentWindowCorrect) { + // Is recovering previous playback? + if (DEBUG) Log.d(TAG, "Playback - Rewinding to recovery time=" + + "[" + getTimeString((int)recoveryPositionMillis) + "]"); + seekTo(recoveryPositionMillis); playQueue.unsetRecovery(currentSourceIndex); + isSynchronizing = false; + + } else if (isSynchronizing && simpleExoPlayer.isCurrentWindowDynamic()) { + if (DEBUG) Log.d(TAG, "Playback - Synchronizing livestream to default time"); + // Is still synchronizing? + seekToDefault(); + + } else if (isSynchronizing && presetStartPositionMillis != 0L) { + if (DEBUG) Log.d(TAG, "Playback - Seeking to preset start " + + "position=[" + presetStartPositionMillis + "]"); + // Has another start position? + seekTo(presetStartPositionMillis); + currentInfo.setStartPosition(0); } + + isSynchronizing = false; } /** @@ -810,11 +820,26 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "Playback - onPlaybackSynchronize() called with " + (info != null ? "available" : "null") + " info, " + "item=[" + item.getTitle() + "], url=[" + item.getUrl() + "]"); + if (simpleExoPlayer == null || playQueue == null) return; + final boolean onPlaybackInitial = currentItem == null; final boolean hasPlayQueueItemChanged = currentItem != item; final boolean hasStreamInfoChanged = currentInfo != info; + + final int currentPlayQueueIndex = playQueue.indexOf(item); + final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex(); + final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount(); + + // when starting playback on the last item when not repeating, maybe auto queue + if (info != null && currentPlayQueueIndex == playQueue.size() - 1 && + getRepeatMode() == Player.REPEAT_MODE_OFF && + PlayerHelper.isAutoQueueEnabled(context)) { + final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams()); + if (autoQueue != null) playQueue.append(autoQueue.getStreams()); + } + // If nothing to synchronize if (!hasPlayQueueItemChanged && !hasStreamInfoChanged) { - return; // Nothing to synchronize + return; } currentItem = item; @@ -824,13 +849,8 @@ public abstract class BasePlayer implements registerView(); initThumbnail(info == null ? item.getThumbnailUrl() : info.getThumbnailUrl()); } - - final int currentPlayQueueIndex = playQueue.indexOf(item); onMetadataChanged(item, info, currentPlayQueueIndex, hasPlayQueueItemChanged); - if (simpleExoPlayer == null) return; - final int currentPlaylistIndex = simpleExoPlayer.getCurrentWindowIndex(); - final int currentPlaylistSize = simpleExoPlayer.getCurrentTimeline().getWindowCount(); // Check if on wrong window if (currentPlayQueueIndex != playQueue.getIndex()) { Log.e(TAG, "Playback - Play Queue may be desynchronized: item " + @@ -844,22 +864,16 @@ public abstract class BasePlayer implements "index=[" + currentPlayQueueIndex + "] with " + "playlist length=[" + currentPlaylistSize + "]"); - // If not playing correct stream, change window position - } else if (currentPlaylistIndex != currentPlayQueueIndex || !isPlaying()) { - final long startPos = info != null ? info.getStartPosition() : C.TIME_UNSET; + // If not playing correct stream, change window position and sets flag + // for synchronizing once window position is corrected + // @see maybeCorrectSeekPosition() + } else if (currentPlaylistIndex != currentPlayQueueIndex || onPlaybackInitial || + !isPlaying()) { if (DEBUG) Log.d(TAG, "Playback - Rewinding to correct" + " index=[" + currentPlayQueueIndex + "]," + - " at=[" + getTimeString((int)startPos) + "]," + " from=[" + currentPlaylistIndex + "], size=[" + currentPlaylistSize + "]."); - simpleExoPlayer.seekTo(currentPlayQueueIndex, startPos); - } - - // when starting playback on the last item when not repeating, maybe auto queue - if (info != null && currentPlayQueueIndex == playQueue.size() - 1 && - getRepeatMode() == Player.REPEAT_MODE_OFF && - PlayerHelper.isAutoQueueEnabled(context)) { - final PlayQueue autoQueue = PlayerHelper.autoQueueOf(info, playQueue.getStreams()); - if (autoQueue != null) playQueue.append(autoQueue.getStreams()); + isSynchronizing = true; + simpleExoPlayer.seekToDefaultPosition(currentPlayQueueIndex); } } @@ -927,9 +941,6 @@ public abstract class BasePlayer implements if (DEBUG) Log.d(TAG, "onPrepared() called with: playWhenReady = [" + playWhenReady + "]"); if (playWhenReady) audioReactor.requestAudioFocus(); changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); - - // On live prepared - if (simpleExoPlayer.isCurrentWindowDynamic()) seekToDefault(); } public void onVideoPlayPause() { @@ -1001,16 +1012,16 @@ public abstract class BasePlayer implements playQueue.setIndex(index); } - public void seekBy(int milliSeconds) { - if (DEBUG) Log.d(TAG, "seekBy() called with: milliSeconds = [" + milliSeconds + "]"); - if (simpleExoPlayer == null || (isCompleted() && milliSeconds > 0) || - ((milliSeconds < 0 && simpleExoPlayer.getCurrentPosition() == 0))) { - return; - } + public void seekTo(long positionMillis) { + if (DEBUG) Log.d(TAG, "seekBy() called with: position = [" + positionMillis + "]"); + if (simpleExoPlayer == null || positionMillis < 0 || + positionMillis > simpleExoPlayer.getDuration()) return; + simpleExoPlayer.seekTo(positionMillis); + } - int progress = (int) (simpleExoPlayer.getCurrentPosition() + milliSeconds); - if (progress < 0) progress = 0; - simpleExoPlayer.seekTo(progress); + public void seekBy(long offsetMillis) { + if (DEBUG) Log.d(TAG, "seekBy() called with: offsetMillis = [" + offsetMillis + "]"); + seekTo(simpleExoPlayer.getCurrentPosition() + offsetMillis); } public boolean isCurrentWindowValid() { @@ -1094,10 +1105,6 @@ public abstract class BasePlayer implements return currentItem == null ? context.getString(R.string.unknown_content) : currentItem.getUploader(); } - public boolean isCompleted() { - return simpleExoPlayer != null && simpleExoPlayer.getPlaybackState() == Player.STATE_ENDED; - } - public boolean isPlaying() { final int state = simpleExoPlayer.getPlaybackState(); return (state == Player.STATE_READY || state == Player.STATE_BUFFERING) @@ -1148,8 +1155,8 @@ public abstract class BasePlayer implements return playQueueAdapter; } - public boolean isPlayerReady() { - return currentState == STATE_PLAYING || currentState == STATE_COMPLETED || currentState == STATE_PAUSED; + public boolean isPrepared() { + return isPrepared; } public boolean isProgressLoopRunning() { diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index dd7e0c71e..90a4a8c9f 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -640,6 +640,7 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR public void onDismiss(PopupMenu menu) { super.onDismiss(menu); if (isPlaying()) hideControls(DEFAULT_CONTROLS_DURATION, 0); + hideSystemUi(); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 123fbfee3..64dc03da6 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -716,7 +716,7 @@ public final class PopupVideoPlayer extends Service { public boolean onDoubleTap(MotionEvent e) { if (DEBUG) Log.d(TAG, "onDoubleTap() called with: e = [" + e + "]" + "rawXy = " + e.getRawX() + ", " + e.getRawY() + ", xy = " + e.getX() + ", " + e.getY()); - if (playerImpl == null || !playerImpl.isPlaying() || !playerImpl.isPlayerReady()) return false; + if (playerImpl == null || !playerImpl.isPlaying()) return false; if (e.getX() > popupWidth / 2) { playerImpl.onFastForward(); diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 1c3ffe911..50248891b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -509,7 +509,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity @Override public void onStopTrackingTouch(SeekBar seekBar) { - if (player != null) player.simpleExoPlayer.seekTo(seekBar.getProgress()); + if (player != null) player.seekTo(seekBar.getProgress()); seekDisplay.setVisibility(View.GONE); seeking = false; } diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index 48b13654c..aa896bb69 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -425,7 +425,7 @@ public abstract class VideoPlayer extends BasePlayer // Create subtitle sources for (final Subtitles subtitle : info.getSubtitles()) { final String mimeType = PlayerHelper.mimeTypesOf(subtitle.getFileType()); - if (mimeType == null || context == null) continue; + if (mimeType == null) continue; final Format textFormat = Format.createTextSampleFormat(null, mimeType, SELECTION_FLAG_AUTOSELECT, PlayerHelper.captionLanguageOf(context, subtitle)); @@ -599,7 +599,7 @@ public abstract class VideoPlayer extends BasePlayer @Override public void onUpdateProgress(int currentProgress, int duration, int bufferPercent) { - if (!isPrepared) return; + if (!isPrepared()) return; if (duration != playbackSeekBar.getMax()) { playbackEndTime.setText(getTimeString(duration)); @@ -624,8 +624,6 @@ public abstract class VideoPlayer extends BasePlayer } protected void onFullScreenButtonClicked() { - if (!isPlayerReady()) return; - changeState(STATE_BLOCKED); } @@ -735,7 +733,7 @@ public abstract class VideoPlayer extends BasePlayer } private void onResizeClicked() { - if (getAspectRatioFrameLayout() != null && context != null) { + if (getAspectRatioFrameLayout() != null) { final int currentResizeMode = getAspectRatioFrameLayout().getResizeMode(); final int newResizeMode = nextResizeMode(currentResizeMode); getAspectRatioFrameLayout().setResizeMode(newResizeMode); @@ -772,7 +770,7 @@ public abstract class VideoPlayer extends BasePlayer public void onStopTrackingTouch(SeekBar seekBar) { if (DEBUG) Log.d(TAG, "onStopTrackingTouch() called with: seekBar = [" + seekBar + "]"); - simpleExoPlayer.seekTo(seekBar.getProgress()); + seekTo(seekBar.getProgress()); if (wasPlaying || simpleExoPlayer.getDuration() == seekBar.getProgress()) simpleExoPlayer.setPlayWhenReady(true); playbackCurrentTime.setText(getTimeString(seekBar.getProgress())); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 50c069b40..170668169 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -307,7 +307,7 @@ public class MediaSourceManager { if (DEBUG) Log.d(TAG, "onPlaybackSynchronize() called."); final PlayQueueItem currentItem = playQueue.getItem(); - if (isBlocked.get() || currentItem == null) return; + if (isBlocked.get() || !isPlaybackReady() || currentItem == null) return; final Consumer onSuccess = info -> syncInternal(currentItem, info); final Consumer onError = throwable -> syncInternal(currentItem, null); @@ -400,8 +400,6 @@ public class MediaSourceManager { /* No exception handling since getLoadedMediaSource guarantees nonnull return */ .subscribe(mediaSource -> onMediaSourceReceived(item, mediaSource)); loaderReactor.add(loader); - } else { - maybeSynchronizePlayer(); } } @@ -467,6 +465,12 @@ public class MediaSourceManager { * Checks if the current playing index contains an expired {@link ManagedMediaSource}. * If so, the expired source is replaced by a {@link PlaceholderMediaSource} and * {@link #loadImmediate()} is called to reload the current item. + *

+ * If not, then the media source at the current index is ready for playback, and + * {@link #maybeSynchronizePlayer()} is called. + *

+ * Under both cases, {@link #maybeSync()} will be called to ensure the listener + * is up-to-date. * */ private void maybeRenewCurrentIndex() { final int currentIndex = playQueue.getIndex(); @@ -475,7 +479,10 @@ public class MediaSourceManager { final ManagedMediaSource currentSource = (ManagedMediaSource) sources.getMediaSource(currentIndex); final PlayQueueItem currentItem = playQueue.getItem(); - if (!currentSource.canReplace(currentItem)) return; + if (!currentSource.canReplace(currentItem)) { + maybeSynchronizePlayer(); + return; + } if (DEBUG) Log.d(TAG, "MediaSource - Reloading currently playing, " + "index=[" + currentIndex + "], item=[" + currentItem.getTitle() + "]"); From bc7188c8a8c8036ed67a93aa2572f5de676721ce Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 15 Mar 2018 23:42:46 -0700 Subject: [PATCH 34/69] -Added media session implementation for all players. -Extracted version numbers in gradle dependencies. -Updated ExoPlayer to 2.7.1. -Updated RxJava to 2.1.10, RxAndroid to 2.0.2 and RxBinding to 2.1.1. -Removed deprecated implementation of media buttons. --- app/build.gradle | 36 +++--- app/src/main/AndroidManifest.xml | 6 - .../newpipe/player/BackgroundPlayer.java | 54 --------- .../org/schabi/newpipe/player/BasePlayer.java | 12 +- .../newpipe/player/helper/AudioReactor.java | 33 ++---- .../player/helper/MediaSessionManager.java | 38 ++++++ .../mediasession/DummyPlaybackPreparer.java | 45 +++++++ .../mediasession/MediaSessionCallback.java | 17 +++ .../mediasession/PlayQueueNavigator.java | 111 ++++++++++++++++++ .../PlayQueuePlaybackController.java | 31 +++++ .../playback/BasePlayerMediaSession.java | 77 ++++++++++++ 11 files changed, 358 insertions(+), 102 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/DummyPlaybackPreparer.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java create mode 100644 app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java diff --git a/app/build.gradle b/app/build.gradle index 952bc3067..9b2569a66 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,6 +49,11 @@ android { ext { supportLibVersion = '27.1.0' + exoPlayerLibVersion = '2.7.1' + roomDbLibVersion = '1.0.0' + leakCanaryVersion = '1.5.4' + okHttpVersion = '1.5.0' + icepickVersion = '3.2.0' } dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') { @@ -73,27 +78,28 @@ dependencies { implementation 'de.hdodenhof:circleimageview:2.2.0' implementation 'com.github.nirhart:ParallaxScroll:dd53d1f9d1' implementation 'com.nononsenseapps:filepicker:4.2.1' - implementation 'com.google.android.exoplayer:exoplayer:2.7.1' + implementation "com.google.android.exoplayer:exoplayer:$exoPlayerLibVersion" + implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion" - debugImplementation 'com.facebook.stetho:stetho:1.5.0' - debugImplementation 'com.facebook.stetho:stetho-urlconnection:1.5.0' + debugImplementation "com.facebook.stetho:stetho:$okHttpVersion" + debugImplementation "com.facebook.stetho:stetho-urlconnection:$okHttpVersion" debugImplementation 'com.android.support:multidex:1.0.3' - implementation 'io.reactivex.rxjava2:rxjava:2.1.7' - implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' - implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0' + implementation 'io.reactivex.rxjava2:rxjava:2.1.10' + implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' + implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1' - implementation 'android.arch.persistence.room:runtime:1.0.0' - implementation 'android.arch.persistence.room:rxjava2:1.0.0' - annotationProcessor 'android.arch.persistence.room:compiler:1.0.0' + implementation "android.arch.persistence.room:runtime:$roomDbLibVersion" + implementation "android.arch.persistence.room:rxjava2:$roomDbLibVersion" + annotationProcessor "android.arch.persistence.room:compiler:$roomDbLibVersion" - implementation 'frankiesardo:icepick:3.2.0' - annotationProcessor 'frankiesardo:icepick-processor:3.2.0' + implementation "frankiesardo:icepick:$icepickVersion" + annotationProcessor "frankiesardo:icepick-processor:$icepickVersion" - debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4' - betaImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' - releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' + debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" + betaImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" + releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" implementation 'com.squareup.okhttp3:okhttp:3.9.1' - debugImplementation 'com.facebook.stetho:stetho-okhttp3:1.5.0' + debugImplementation "com.facebook.stetho:stetho-okhttp3:$okHttpVersion" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 18b3222a0..1be8c1f2c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -43,12 +43,6 @@ android:launchMode="singleTask" android:label="@string/title_activity_background_player"/> - - - - - - = Build.VERSION_CODES.O; - private static final boolean CAN_USE_MEDIA_BUTTONS = - Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1; - private static final String MEDIA_BUTTON_DEPRECATED_ERROR = - "registerMediaButtonEventReceiver has been deprecated and maybe not supported anymore."; - private static final int DUCK_DURATION = 1500; private static final float DUCK_AUDIO_TO = .2f; @@ -42,7 +37,8 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au private final AudioFocusRequest request; - public AudioReactor(@NonNull final Context context, @NonNull final SimpleExoPlayer player) { + public AudioReactor(@NonNull final Context context, + @NonNull final SimpleExoPlayer player) { this.player = player; this.context = context; this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); @@ -59,6 +55,11 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au } } + public void dispose() { + abandonAudioFocus(); + player.removeAudioDebugListener(this); + } + /*////////////////////////////////////////////////////////////////////////// // Audio Manager //////////////////////////////////////////////////////////////////////////*/ @@ -91,22 +92,6 @@ public class AudioReactor implements AudioManager.OnAudioFocusChangeListener, Au audioManager.setStreamVolume(STREAM_TYPE, volume, 0); } - public void registerMediaButtonEventReceiver(ComponentName componentName) { - if (CAN_USE_MEDIA_BUTTONS) { - audioManager.registerMediaButtonEventReceiver(componentName); - } else { - Log.e(TAG, MEDIA_BUTTON_DEPRECATED_ERROR); - } - } - - public void unregisterMediaButtonEventReceiver(ComponentName componentName) { - if (CAN_USE_MEDIA_BUTTONS) { - audioManager.unregisterMediaButtonEventReceiver(componentName); - } else { - Log.e(TAG, MEDIA_BUTTON_DEPRECATED_ERROR); - } - } - /*////////////////////////////////////////////////////////////////////////// // AudioFocus //////////////////////////////////////////////////////////////////////////*/ diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java new file mode 100644 index 000000000..8405e45fd --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/MediaSessionManager.java @@ -0,0 +1,38 @@ +package org.schabi.newpipe.player.helper; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.v4.media.session.MediaSessionCompat; + +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; + +import org.schabi.newpipe.player.mediasession.DummyPlaybackPreparer; +import org.schabi.newpipe.player.mediasession.MediaSessionCallback; +import org.schabi.newpipe.player.mediasession.PlayQueueNavigator; +import org.schabi.newpipe.player.mediasession.PlayQueuePlaybackController; + +public class MediaSessionManager { + private static final String TAG = "MediaSessionManager"; + + private final MediaSessionCompat mediaSession; + private final MediaSessionConnector sessionConnector; + + public MediaSessionManager(@NonNull final Context context, + @NonNull final Player player, + @NonNull final MediaSessionCallback callback) { + this.mediaSession = new MediaSessionCompat(context, TAG); + this.sessionConnector = new MediaSessionConnector(mediaSession, + new PlayQueuePlaybackController(callback)); + this.sessionConnector.setQueueNavigator(new PlayQueueNavigator(mediaSession, callback)); + this.sessionConnector.setPlayer(player, new DummyPlaybackPreparer()); + } + + public MediaSessionCompat getMediaSession() { + return mediaSession; + } + + public MediaSessionConnector getSessionConnector() { + return sessionConnector; + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/DummyPlaybackPreparer.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/DummyPlaybackPreparer.java new file mode 100644 index 000000000..431a90d8a --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/DummyPlaybackPreparer.java @@ -0,0 +1,45 @@ +package org.schabi.newpipe.player.mediasession; + +import android.net.Uri; +import android.os.Bundle; +import android.os.ResultReceiver; + +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; + +public class DummyPlaybackPreparer implements MediaSessionConnector.PlaybackPreparer { + @Override + public long getSupportedPrepareActions() { + return 0; + } + + @Override + public void onPrepare() { + + } + + @Override + public void onPrepareFromMediaId(String mediaId, Bundle extras) { + + } + + @Override + public void onPrepareFromSearch(String query, Bundle extras) { + + } + + @Override + public void onPrepareFromUri(Uri uri, Bundle extras) { + + } + + @Override + public String[] getCommands() { + return new String[0]; + } + + @Override + public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) { + + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java new file mode 100644 index 000000000..a1a57a87d --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/MediaSessionCallback.java @@ -0,0 +1,17 @@ +package org.schabi.newpipe.player.mediasession; + +import android.support.v4.media.MediaDescriptionCompat; + +public interface MediaSessionCallback { + void onSkipToPrevious(); + void onSkipToNext(); + void onSkipToIndex(final int index); + + int getCurrentPlayingIndex(); + int getQueueSize(); + MediaDescriptionCompat getQueueMetadata(final int index); + + void onPlay(); + void onPause(); + void onSetShuffle(final boolean isShuffled); +} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java new file mode 100644 index 000000000..429c26fd9 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueueNavigator.java @@ -0,0 +1,111 @@ +package org.schabi.newpipe.player.mediasession; + +import android.os.Bundle; +import android.os.ResultReceiver; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.media.session.MediaSessionCompat; + +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector; +import com.google.android.exoplayer2.util.Util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_NEXT; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; +import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM; + + +public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator { + public static final int DEFAULT_MAX_QUEUE_SIZE = 10; + + private final MediaSessionCompat mediaSession; + private final MediaSessionCallback callback; + private final int maxQueueSize; + + private long activeQueueItemId; + + public PlayQueueNavigator(@NonNull final MediaSessionCompat mediaSession, + @NonNull final MediaSessionCallback callback) { + this.mediaSession = mediaSession; + this.callback = callback; + this.maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; + + this.activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; + } + + @Override + public long getSupportedQueueNavigatorActions(@Nullable Player player) { + return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM; + } + + @Override + public void onTimelineChanged(Player player) { + publishFloatingQueueWindow(); + } + + @Override + public void onCurrentWindowIndexChanged(Player player) { + if (activeQueueItemId == MediaSessionCompat.QueueItem.UNKNOWN_ID + || player.getCurrentTimeline().getWindowCount() > maxQueueSize) { + publishFloatingQueueWindow(); + } else if (!player.getCurrentTimeline().isEmpty()) { + activeQueueItemId = player.getCurrentWindowIndex(); + } + } + + @Override + public long getActiveQueueItemId(@Nullable Player player) { + return callback.getCurrentPlayingIndex(); + } + + @Override + public void onSkipToPrevious(Player player) { + callback.onSkipToPrevious(); + } + + @Override + public void onSkipToQueueItem(Player player, long id) { + callback.onSkipToIndex((int) id); + } + + @Override + public void onSkipToNext(Player player) { + callback.onSkipToNext(); + } + + private void publishFloatingQueueWindow() { + if (callback.getQueueSize() == 0) { + mediaSession.setQueue(Collections.emptyList()); + activeQueueItemId = MediaSessionCompat.QueueItem.UNKNOWN_ID; + return; + } + + // Yes this is almost a copypasta, got a problem with that? =\ + int windowCount = callback.getQueueSize(); + int currentWindowIndex = callback.getCurrentPlayingIndex(); + int queueSize = Math.min(maxQueueSize, windowCount); + int startIndex = Util.constrainValue(currentWindowIndex - ((queueSize - 1) / 2), 0, + windowCount - queueSize); + + List queue = new ArrayList<>(); + for (int i = startIndex; i < startIndex + queueSize; i++) { + queue.add(new MediaSessionCompat.QueueItem(callback.getQueueMetadata(i), i)); + } + mediaSession.setQueue(queue); + activeQueueItemId = currentWindowIndex; + } + + @Override + public String[] getCommands() { + return new String[0]; + } + + @Override + public void onCommand(Player player, String command, Bundle extras, ResultReceiver cb) { + + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java new file mode 100644 index 000000000..2aa41bd63 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/mediasession/PlayQueuePlaybackController.java @@ -0,0 +1,31 @@ +package org.schabi.newpipe.player.mediasession; + +import android.support.v4.media.session.PlaybackStateCompat; + +import com.google.android.exoplayer2.Player; +import com.google.android.exoplayer2.ext.mediasession.DefaultPlaybackController; + +public class PlayQueuePlaybackController extends DefaultPlaybackController { + private final MediaSessionCallback callback; + + public PlayQueuePlaybackController(final MediaSessionCallback callback) { + super(); + this.callback = callback; + } + + @Override + public void onPlay(Player player) { + callback.onPlay(); + } + + @Override + public void onPause(Player player) { + callback.onPause(); + } + + @Override + public void onSetShuffleMode(Player player, int shuffleMode) { + callback.onSetShuffle(shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_ALL + || shuffleMode == PlaybackStateCompat.SHUFFLE_MODE_GROUP); + } +} diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java new file mode 100644 index 000000000..07504542c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java @@ -0,0 +1,77 @@ +package org.schabi.newpipe.player.playback; + +import android.net.Uri; +import android.support.v4.media.MediaDescriptionCompat; + +import org.schabi.newpipe.player.BasePlayer; +import org.schabi.newpipe.player.mediasession.MediaSessionCallback; +import org.schabi.newpipe.playlist.PlayQueueItem; + +public class BasePlayerMediaSession implements MediaSessionCallback { + private BasePlayer player; + + public BasePlayerMediaSession(final BasePlayer player) { + this.player = player; + } + + @Override + public void onSkipToPrevious() { + player.onPlayPrevious(); + } + + @Override + public void onSkipToNext() { + player.onPlayNext(); + } + + @Override + public void onSkipToIndex(int index) { + if (player.getPlayQueue() == null) return; + player.onSelected(player.getPlayQueue().getItem(index)); + } + + @Override + public int getCurrentPlayingIndex() { + if (player.getPlayQueue() == null) return -1; + return player.getPlayQueue().getIndex(); + } + + @Override + public int getQueueSize() { + if (player.getPlayQueue() == null) return -1; + return player.getPlayQueue().size(); + } + + @Override + public MediaDescriptionCompat getQueueMetadata(int index) { + if (player.getPlayQueue() == null || player.getPlayQueue().getItem(index) == null) { + return null; + } + + final PlayQueueItem item = player.getPlayQueue().getItem(index); + MediaDescriptionCompat.Builder descriptionBuilder = new MediaDescriptionCompat.Builder() + .setMediaId(String.valueOf(index)) + .setTitle(item.getTitle()) + .setSubtitle(item.getUploader()); + + final Uri thumbnailUri = Uri.parse(item.getThumbnailUrl()); + if (thumbnailUri != null) descriptionBuilder.setIconUri(thumbnailUri); + + return descriptionBuilder.build(); + } + + @Override + public void onPlay() { + if (!player.isPlaying()) player.onVideoPlayPause(); + } + + @Override + public void onPause() { + if (player.isPlaying()) player.onVideoPlayPause(); + } + + @Override + public void onSetShuffle(boolean isShuffled) { + player.onShuffleModeEnabledChanged(isShuffled); + } +} From 5167fe078bb066677644393bc52e6117d1e83c89 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Sat, 17 Mar 2018 16:04:02 -0700 Subject: [PATCH 35/69] -Refactored synchronization checks out from MediaSourceManager to ManagedMediaSource. -Fixed null input causing potential NPE on PlayQueueItem. --- .../player/mediasource/FailedMediaSource.java | 8 ++++- .../player/mediasource/LoadedMediaSource.java | 10 ++++-- .../mediasource/ManagedMediaSource.java | 18 ++++++++++- .../mediasource/PlaceholderMediaSource.java | 8 ++++- .../player/playback/MediaSourceManager.java | 21 ++++--------- .../newpipe/playlist/PlayQueueItem.java | 31 ++++++++++--------- 6 files changed, 61 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java index d07baf2a7..5f029cc50 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/FailedMediaSource.java @@ -72,7 +72,13 @@ public class FailedMediaSource implements ManagedMediaSource { public void releaseSource() {} @Override - public boolean canReplace(@NonNull final PlayQueueItem newIdentity) { + public boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, + final boolean isInterruptable) { return newIdentity != playQueueItem || canRetry(); } + + @Override + public boolean isStreamEqual(@NonNull PlayQueueItem stream) { + return playQueueItem == stream; + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java index f523667f9..fe7508ecc 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/LoadedMediaSource.java @@ -59,7 +59,13 @@ public class LoadedMediaSource implements ManagedMediaSource { } @Override - public boolean canReplace(@NonNull final PlayQueueItem newIdentity) { - return newIdentity != stream || isExpired(); + public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity, + final boolean isInterruptable) { + return newIdentity != stream || (isInterruptable && isExpired()); + } + + @Override + public boolean isStreamEqual(@NonNull PlayQueueItem stream) { + return this.stream == stream; } } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java index 3bb7ca429..46fd149bb 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/ManagedMediaSource.java @@ -7,5 +7,21 @@ import com.google.android.exoplayer2.source.MediaSource; import org.schabi.newpipe.playlist.PlayQueueItem; public interface ManagedMediaSource extends MediaSource { - boolean canReplace(@NonNull final PlayQueueItem newIdentity); + /** + * Determines whether or not this {@link ManagedMediaSource} can be replaced. + * + * @param newIdentity a stream the {@link ManagedMediaSource} should encapsulate over, if + * it is different from the existing stream in the + * {@link ManagedMediaSource}, then it should be replaced. + * @param isInterruptable specifies if this {@link ManagedMediaSource} potentially + * being played. + * */ + boolean shouldBeReplacedWith(@NonNull final PlayQueueItem newIdentity, + final boolean isInterruptable); + + /** + * Determines if the {@link PlayQueueItem} is the one the + * {@link ManagedMediaSource} encapsulates over. + * */ + boolean isStreamEqual(@NonNull final PlayQueueItem stream); } diff --git a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java index 0d3436a01..2c57f2f9c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java +++ b/app/src/main/java/org/schabi/newpipe/player/mediasource/PlaceholderMediaSource.java @@ -19,7 +19,13 @@ public class PlaceholderMediaSource implements ManagedMediaSource { @Override public void releaseSource() {} @Override - public boolean canReplace(@NonNull final PlayQueueItem newIdentity) { + public boolean shouldBeReplacedWith(@NonNull PlayQueueItem newIdentity, + final boolean isInterruptable) { return true; } + + @Override + public boolean isStreamEqual(@NonNull PlayQueueItem stream) { + return false; + } } diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java index 170668169..477358113 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/MediaSourceManager.java @@ -268,15 +268,10 @@ public class MediaSourceManager { private boolean isPlaybackReady() { if (sources.getSize() != playQueue.size()) return false; - final MediaSource mediaSource = sources.getMediaSource(playQueue.getIndex()); + final ManagedMediaSource mediaSource = + (ManagedMediaSource) sources.getMediaSource(playQueue.getIndex()); final PlayQueueItem playQueueItem = playQueue.getItem(); - - if (mediaSource instanceof LoadedMediaSource) { - return playQueueItem == ((LoadedMediaSource) mediaSource).getStream(); - } else if (mediaSource instanceof FailedMediaSource) { - return playQueueItem == ((FailedMediaSource) mediaSource).getStream(); - } - return false; + return mediaSource.isStreamEqual(playQueueItem); } private void maybeBlock() { @@ -453,12 +448,8 @@ public class MediaSourceManager { if (index == -1 || index >= sources.getSize()) return false; final ManagedMediaSource mediaSource = (ManagedMediaSource) sources.getMediaSource(index); - - if (index == playQueue.getIndex() && mediaSource instanceof LoadedMediaSource) { - return item != ((LoadedMediaSource) mediaSource).getStream(); - } else { - return mediaSource.canReplace(item); - } + return mediaSource.shouldBeReplacedWith(item, + /*mightBeInProgress=*/index != playQueue.getIndex()); } /** @@ -479,7 +470,7 @@ public class MediaSourceManager { final ManagedMediaSource currentSource = (ManagedMediaSource) sources.getMediaSource(currentIndex); final PlayQueueItem currentItem = playQueue.getItem(); - if (!currentSource.canReplace(currentItem)) { + if (!currentSource.shouldBeReplacedWith(currentItem, /*canInterruptOnRenew=*/true)) { maybeSynchronizePlayer(); return; } diff --git a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java index 752dc223d..df4d19720 100644 --- a/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java +++ b/app/src/main/java/org/schabi/newpipe/playlist/PlayQueueItem.java @@ -11,20 +11,19 @@ import org.schabi.newpipe.util.ExtractorHelper; import java.io.Serializable; import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; public class PlayQueueItem implements Serializable { - final public static long RECOVERY_UNSET = Long.MIN_VALUE; + public final static long RECOVERY_UNSET = Long.MIN_VALUE; + private final static String EMPTY_STRING = ""; - final private String title; - final private String url; + @NonNull final private String title; + @NonNull final private String url; final private int serviceId; final private long duration; - final private String thumbnailUrl; - final private String uploader; - final private StreamType streamType; + @NonNull final private String thumbnailUrl; + @NonNull final private String uploader; + @NonNull final private StreamType streamType; private long recoveryPosition; private Throwable error; @@ -42,15 +41,16 @@ public class PlayQueueItem implements Serializable { item.getThumbnailUrl(), item.getUploaderName(), item.getStreamType()); } - private PlayQueueItem(final String name, final String url, final int serviceId, - final long duration, final String thumbnailUrl, final String uploader, - final StreamType streamType) { - this.title = name; - this.url = url; + private PlayQueueItem(@Nullable final String name, @Nullable final String url, + final int serviceId, final long duration, + @Nullable final String thumbnailUrl, @Nullable final String uploader, + @NonNull final StreamType streamType) { + this.title = name != null ? name : EMPTY_STRING; + this.url = url != null ? url : EMPTY_STRING; this.serviceId = serviceId; this.duration = duration; - this.thumbnailUrl = thumbnailUrl; - this.uploader = uploader; + this.thumbnailUrl = thumbnailUrl != null ? thumbnailUrl : EMPTY_STRING; + this.uploader = uploader != null ? uploader : EMPTY_STRING; this.streamType = streamType; this.recoveryPosition = RECOVERY_UNSET; @@ -84,6 +84,7 @@ public class PlayQueueItem implements Serializable { return uploader; } + @NonNull public StreamType getStreamType() { return streamType; } From 7dc176edcb9ae75c8f3ee78386d21851de4dc27a Mon Sep 17 00:00:00 2001 From: Tobias Groza Date: Mon, 19 Mar 2018 15:01:13 +0000 Subject: [PATCH 36/69] Translated using Weblate (German) Currently translated at 93.2% (320 of 343 strings) --- app/src/main/res/values-de/strings.xml | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index df414b781..acb75efd1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -376,4 +376,30 @@ Abbrechen Normale Schriftgröße Stream-Datei herunterladen - + Benutze schnelle ungenaue Suche + Ungenaues Suchen erlaubt dem Player die Positionen schneller mit geringerer Genauigkeit zu suchen + Datei + + Ungültiges Verzeichnis + Datei existiert nicht oder nicht ausreichende Rechte um sie zu lesen oder zu beschreiben + Dateiname darf nicht leer sein + Ein Fehler ist aufgetreten: %1$s + + Automatisch erzeugt + Kleinere Schriftgröße + Größere Schriftgröße + + LeakCanary aktivieren + Import von + Export nach + + Importiere… + Exportiere… + + Datei importieren + Vorheriger Export + + Beachte, dass diese Aktion sehr Netzwerk intensiv sein kann. +\n +\nMöchtest du fortfahren? + From e885822a3484d5404fe6cce51cb0eb87cadccf4f Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 21 Mar 2018 00:11:54 -0700 Subject: [PATCH 37/69] -Added playback speed control dialog to allow full user control over player tempo and pitch parameters. -Changed tempo and pitch button in service player activity and tempo button in main video player to open speed control dialog. -Changed LIVE button to be no longer clickable when player position is at or beyond default position. -Changed main video player to use AppCompatActivity rather than Activity. -Fixed video player tempo button not updating when player speed parameters change. -Fixed player crashing on lower sdk versions due to no MediaButtonReceiver, added intent back to manifest. -Fixed inconsistent gradle library naming. -Fixed stetho dependencies incorrect version. --- app/build.gradle | 23 +- app/src/main/AndroidManifest.xml | 6 + .../org/schabi/newpipe/player/BasePlayer.java | 21 +- .../newpipe/player/MainVideoPlayer.java | 22 +- .../newpipe/player/ServicePlayerActivity.java | 66 +--- .../schabi/newpipe/player/VideoPlayer.java | 10 +- .../helper/PlaybackParameterDialog.java | 348 ++++++++++++++++++ .../res/layout/dialog_playback_parameter.xml | 313 ++++++++++++++++ app/src/main/res/values/strings.xml | 8 + 9 files changed, 755 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java create mode 100644 app/src/main/res/layout/dialog_playback_parameter.xml diff --git a/app/build.gradle b/app/build.gradle index 9b2569a66..5c434c30c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,9 +51,10 @@ ext { supportLibVersion = '27.1.0' exoPlayerLibVersion = '2.7.1' roomDbLibVersion = '1.0.0' - leakCanaryVersion = '1.5.4' - okHttpVersion = '1.5.0' - icepickVersion = '3.2.0' + leakCanaryLibVersion = '1.5.4' + okHttpLibVersion = '1.5.0' + icepickLibVersion = '3.2.0' + stethoLibVersion = '1.5.0' } dependencies { androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') { @@ -81,8 +82,8 @@ dependencies { implementation "com.google.android.exoplayer:exoplayer:$exoPlayerLibVersion" implementation "com.google.android.exoplayer:extension-mediasession:$exoPlayerLibVersion" - debugImplementation "com.facebook.stetho:stetho:$okHttpVersion" - debugImplementation "com.facebook.stetho:stetho-urlconnection:$okHttpVersion" + debugImplementation "com.facebook.stetho:stetho:$stethoLibVersion" + debugImplementation "com.facebook.stetho:stetho-urlconnection:$stethoLibVersion" debugImplementation 'com.android.support:multidex:1.0.3' implementation 'io.reactivex.rxjava2:rxjava:2.1.10' @@ -93,13 +94,13 @@ dependencies { implementation "android.arch.persistence.room:rxjava2:$roomDbLibVersion" annotationProcessor "android.arch.persistence.room:compiler:$roomDbLibVersion" - implementation "frankiesardo:icepick:$icepickVersion" - annotationProcessor "frankiesardo:icepick-processor:$icepickVersion" + implementation "frankiesardo:icepick:$icepickLibVersion" + annotationProcessor "frankiesardo:icepick-processor:$icepickLibVersion" - debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryVersion" - betaImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" - releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryVersion" + debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakCanaryLibVersion" + betaImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion" + releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$leakCanaryLibVersion" implementation 'com.squareup.okhttp3:okhttp:3.9.1' - debugImplementation "com.facebook.stetho:stetho-okhttp3:$okHttpVersion" + debugImplementation "com.facebook.stetho:stetho-okhttp3:$okHttpLibVersion" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1be8c1f2c..1edd67d24 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -28,6 +28,12 @@ + + + + + + = currentTimeline.getWindowCount()) { + return false; + } + + Timeline.Window timelineWindow = new Timeline.Window(); + currentTimeline.getWindow(currentWindowIndex, timelineWindow); + return timelineWindow.getDefaultPositionMs() <= simpleExoPlayer.getCurrentPosition(); + } + public boolean isPlaying() { final int state = simpleExoPlayer.getPlaybackState(); return (state == Player.STATE_READY || state == Player.STATE_BUFFERING) diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index 90a4a8c9f..cbc4b8230 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -19,7 +19,6 @@ package org.schabi.newpipe.player; -import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -33,6 +32,7 @@ import android.preference.PreferenceManager; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.DisplayMetrics; @@ -49,6 +49,7 @@ import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.SubtitleView; @@ -57,6 +58,7 @@ import org.schabi.newpipe.R; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; +import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.playlist.PlayQueue; import org.schabi.newpipe.playlist.PlayQueueItem; @@ -87,7 +89,8 @@ import static org.schabi.newpipe.util.StateSaver.KEY_SAVED_STATE; * * @author mauriciocolli */ -public final class MainVideoPlayer extends Activity implements StateSaver.WriteRead { +public final class MainVideoPlayer extends AppCompatActivity + implements StateSaver.WriteRead, PlaybackParameterDialog.Callback { private static final String TAG = ".MainVideoPlayer"; private static final boolean DEBUG = BasePlayer.DEBUG; @@ -340,6 +343,15 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR } } + //////////////////////////////////////////////////////////////////////////// + // Playback Parameters Listener + //////////////////////////////////////////////////////////////////////////// + + @Override + public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) { + if (playerImpl != null) playerImpl.setPlaybackParameters(playbackTempo, playbackPitch); + } + /////////////////////////////////////////////////////////////////////////// @SuppressWarnings({"unused", "WeakerAccess"}) @@ -630,6 +642,12 @@ public final class MainVideoPlayer extends Activity implements StateSaver.WriteR showControlsThenHide(); } + @Override + public void onPlaybackSpeedClicked() { + PlaybackParameterDialog.newInstance(getPlaybackSpeed(), getPlaybackPitch()) + .show(getSupportFragmentManager(), TAG); + } + @Override public void onStopTrackingTouch(SeekBar seekBar) { super.onStopTrackingTouch(seekBar); diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 50248891b..1f850944d 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -31,6 +31,7 @@ import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; import org.schabi.newpipe.fragments.local.dialog.PlaylistAppendDialog; import org.schabi.newpipe.player.event.PlayerEventListener; +import org.schabi.newpipe.player.helper.PlaybackParameterDialog; import org.schabi.newpipe.playlist.PlayQueueItem; import org.schabi.newpipe.playlist.PlayQueueItemBuilder; import org.schabi.newpipe.playlist.PlayQueueItemHolder; @@ -43,7 +44,8 @@ import static org.schabi.newpipe.player.helper.PlayerHelper.formatPitch; import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed; public abstract class ServicePlayerActivity extends AppCompatActivity - implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, View.OnClickListener { + implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, + View.OnClickListener, PlaybackParameterDialog.Callback { private boolean serviceBound; private ServiceConnection serviceConnection; @@ -57,8 +59,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity //////////////////////////////////////////////////////////////////////////// private static final int RECYCLER_ITEM_POPUP_MENU_GROUP_ID = 47; - private static final int PLAYBACK_SPEED_POPUP_MENU_GROUP_ID = 61; - private static final int PLAYBACK_PITCH_POPUP_MENU_GROUP_ID = 97; private static final int SMOOTH_SCROLL_MAXIMUM_DISTANCE = 80; @@ -85,9 +85,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity private ProgressBar progressBar; private TextView playbackSpeedButton; - private PopupMenu playbackSpeedPopupMenu; private TextView playbackPitchButton; - private PopupMenu playbackPitchPopupMenu; //////////////////////////////////////////////////////////////////////////// // Abstracts @@ -317,45 +315,6 @@ public abstract class ServicePlayerActivity extends AppCompatActivity shuffleButton.setOnClickListener(this); playbackSpeedButton.setOnClickListener(this); playbackPitchButton.setOnClickListener(this); - - playbackSpeedPopupMenu = new PopupMenu(this, playbackSpeedButton); - playbackPitchPopupMenu = new PopupMenu(this, playbackPitchButton); - buildPlaybackSpeedMenu(); - buildPlaybackPitchMenu(); - } - - private void buildPlaybackSpeedMenu() { - if (playbackSpeedPopupMenu == null) return; - - playbackSpeedPopupMenu.getMenu().removeGroup(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID); - for (int i = 0; i < BasePlayer.PLAYBACK_SPEEDS.length; i++) { - final float playbackSpeed = BasePlayer.PLAYBACK_SPEEDS[i]; - final String formattedSpeed = formatSpeed(playbackSpeed); - final MenuItem item = playbackSpeedPopupMenu.getMenu().add(PLAYBACK_SPEED_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedSpeed); - item.setOnMenuItemClickListener(menuItem -> { - if (player == null) return false; - - player.setPlaybackSpeed(playbackSpeed); - return true; - }); - } - } - - private void buildPlaybackPitchMenu() { - if (playbackPitchPopupMenu == null) return; - - playbackPitchPopupMenu.getMenu().removeGroup(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID); - for (int i = 0; i < BasePlayer.PLAYBACK_PITCHES.length; i++) { - final float playbackPitch = BasePlayer.PLAYBACK_PITCHES[i]; - final String formattedPitch = formatPitch(playbackPitch); - final MenuItem item = playbackPitchPopupMenu.getMenu().add(PLAYBACK_PITCH_POPUP_MENU_GROUP_ID, i, Menu.NONE, formattedPitch); - item.setOnMenuItemClickListener(menuItem -> { - if (player == null) return false; - - player.setPlaybackPitch(playbackPitch); - return true; - }); - } } private void buildItemPopupMenu(final PlayQueueItem item, final View view) { @@ -474,10 +433,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity player.onShuffleClicked(); } else if (view.getId() == playbackSpeedButton.getId()) { - playbackSpeedPopupMenu.show(); + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), + player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag()); } else if (view.getId() == playbackPitchButton.getId()) { - playbackPitchPopupMenu.show(); + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), + player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag()); } else if (view.getId() == metadata.getId()) { scrollToSelected(); @@ -488,6 +449,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity } } + //////////////////////////////////////////////////////////////////////////// + // Playback Parameters Listener + //////////////////////////////////////////////////////////////////////////// + + @Override + public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) { + if (player != null) player.setPlaybackParameters(playbackTempo, playbackPitch); + } + //////////////////////////////////////////////////////////////////////////// // Seekbar Listener //////////////////////////////////////////////////////////////////////////// @@ -539,6 +509,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity progressSeekBar.setProgress(currentProgress); progressCurrentTime.setText(Localization.getDurationString(currentProgress / 1000)); } + + if (player != null) { + progressLiveSync.setClickable(!player.isLiveEdge()); + } } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java index aa896bb69..b019ea91e 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayer.java @@ -49,6 +49,7 @@ import android.widget.TextView; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MergingMediaSource; @@ -523,6 +524,12 @@ public abstract class VideoPlayer extends BasePlayer onTextTrackUpdate(); } + @Override + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { + super.onPlaybackParametersChanged(playbackParameters); + playbackSpeedTextView.setText(formatSpeed(playbackParameters.speed)); + } + @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { if (DEBUG) { @@ -615,6 +622,7 @@ public abstract class VideoPlayer extends BasePlayer if (DEBUG && bufferPercent % 20 == 0) { //Limit log Log.d(TAG, "updateProgress() called with: isVisible = " + isControlsVisible() + ", currentProgress = [" + currentProgress + "], duration = [" + duration + "], bufferPercent = [" + bufferPercent + "]"); } + playbackLiveSync.setClickable(!isLiveEdge()); } @Override @@ -718,7 +726,7 @@ public abstract class VideoPlayer extends BasePlayer wasPlaying = simpleExoPlayer.getPlayWhenReady(); } - private void onPlaybackSpeedClicked() { + public void onPlaybackSpeedClicked() { if (DEBUG) Log.d(TAG, "onPlaybackSpeedClicked() called"); playbackSpeedPopupMenu.show(); isSomePopupMenuVisible = true; diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java new file mode 100644 index 000000000..8a0a8a86c --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -0,0 +1,348 @@ +package org.schabi.newpipe.player.helper; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.util.Log; +import android.view.View; +import android.widget.CheckBox; +import android.widget.SeekBar; +import android.widget.TextView; + +import org.schabi.newpipe.R; + +import static org.schabi.newpipe.player.BasePlayer.DEBUG; + +public class PlaybackParameterDialog extends DialogFragment { + private static final String TAG = "PlaybackParameterDialog"; + + public static final float MINIMUM_PLAYBACK_VALUE = 0.25f; + public static final float MAXIMUM_PLAYBACK_VALUE = 3.00f; + + public static final String STEP_UP_SIGN = "+"; + public static final String STEP_DOWN_SIGN = "-"; + public static final float PLAYBACK_STEP_VALUE = 0.05f; + + public static final float NIGHTCORE_TEMPO = 1.20f; + public static final float NIGHTCORE_PITCH_LOWER = 1.15f; + public static final float NIGHTCORE_PITCH_UPPER = 1.25f; + + public static final float DEFAULT_TEMPO = 1.00f; + public static final float DEFAULT_PITCH = 1.00f; + + private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; + private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; + + public interface Callback { + void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch); + } + + private Callback callback; + + private float initialTempo = DEFAULT_TEMPO; + private float initialPitch = DEFAULT_PITCH; + + private SeekBar tempoSlider; + private TextView tempoMinimumText; + private TextView tempoMaximumText; + private TextView tempoCurrentText; + private TextView tempoStepDownText; + private TextView tempoStepUpText; + + private SeekBar pitchSlider; + private TextView pitchMinimumText; + private TextView pitchMaximumText; + private TextView pitchCurrentText; + private TextView pitchStepDownText; + private TextView pitchStepUpText; + + private CheckBox unhookingCheckbox; + + private TextView nightCorePresetText; + private TextView resetPresetText; + + public static PlaybackParameterDialog newInstance(final float playbackTempo, + final float playbackPitch) { + PlaybackParameterDialog dialog = new PlaybackParameterDialog(); + dialog.initialTempo = playbackTempo; + dialog.initialPitch = playbackPitch; + return dialog; + } + + /*////////////////////////////////////////////////////////////////////////// + // Lifecycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context != null && context instanceof Callback) { + callback = (Callback) context; + } else { + dismiss(); + } + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + initialTempo = savedInstanceState.getFloat(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); + initialPitch = savedInstanceState.getFloat(INITIAL_PITCH_KEY, DEFAULT_PITCH); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putFloat(INITIAL_TEMPO_KEY, initialTempo); + outState.putFloat(INITIAL_PITCH_KEY, initialPitch); + } + + /*////////////////////////////////////////////////////////////////////////// + // Dialog + //////////////////////////////////////////////////////////////////////////*/ + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); + setupView(view); + + final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) + .setTitle(R.string.playback_speed_control) + .setView(view) + .setCancelable(true) + .setNegativeButton(R.string.cancel, (dialogInterface, i) -> + setPlaybackParameters(initialTempo, initialPitch)) + .setPositiveButton(R.string.finish, (dialogInterface, i) -> + setPlaybackParameters(getCurrentTempo(), getCurrentPitch())); + + return dialogBuilder.create(); + } + + /*////////////////////////////////////////////////////////////////////////// + // Dialog Builder + //////////////////////////////////////////////////////////////////////////*/ + + private void setupView(@NonNull View rootView) { + setupHookingControl(rootView); + setupTempoControl(rootView); + setupPitchControl(rootView); + setupPresetControl(rootView); + } + + private void setupTempoControl(@NonNull View rootView) { + tempoSlider = rootView.findViewById(R.id.tempoSeekbar); + tempoMinimumText = rootView.findViewById(R.id.tempoMinimumText); + tempoMaximumText = rootView.findViewById(R.id.tempoMaximumText); + tempoCurrentText = rootView.findViewById(R.id.tempoCurrentText); + tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); + tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); + + tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo)); + tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); + tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); + + tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE)); + tempoStepUpText.setOnClickListener(view -> + setTempo(getCurrentTempo() + PLAYBACK_STEP_VALUE)); + + tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE)); + tempoStepDownText.setOnClickListener(view -> + setTempo(getCurrentTempo() - PLAYBACK_STEP_VALUE)); + + tempoSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE)); + tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialTempo)); + tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener()); + } + + private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() { + return new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + final float currentTempo = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress); + if (fromUser) { // this change is first in chain + setTempo(currentTempo); + } else { + setPlaybackParameters(currentTempo, getCurrentPitch()); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do Nothing. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do Nothing. + } + }; + } + + private void setupPitchControl(@NonNull View rootView) { + pitchSlider = rootView.findViewById(R.id.pitchSeekbar); + pitchMinimumText = rootView.findViewById(R.id.pitchMinimumText); + pitchMaximumText = rootView.findViewById(R.id.pitchMaximumText); + pitchCurrentText = rootView.findViewById(R.id.pitchCurrentText); + pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); + pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); + + pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch)); + pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); + pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); + + pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE)); + pitchStepUpText.setOnClickListener(view -> + setPitch(getCurrentPitch() + PLAYBACK_STEP_VALUE)); + + pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE)); + pitchStepDownText.setOnClickListener(view -> + setPitch(getCurrentPitch() - PLAYBACK_STEP_VALUE)); + + pitchSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE)); + pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialPitch)); + pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); + } + + private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() { + return new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + final float currentPitch = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress); + if (fromUser) { // this change is first in chain + setPitch(currentPitch); + } else { + setPlaybackParameters(getCurrentTempo(), currentPitch); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do Nothing. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do Nothing. + } + }; + } + + private void setupHookingControl(@NonNull View rootView) { + unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); + unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + if (isChecked) return; + // When unchecked, slide back to the minimum of current tempo or pitch + final float minimum = Math.min(getCurrentPitch(), getCurrentTempo()); + setSliders(minimum); + }); + } + + private void setupPresetControl(@NonNull View rootView) { + nightCorePresetText = rootView.findViewById(R.id.presetNightcore); + nightCorePresetText.setOnClickListener(view -> { + final float randomPitch = NIGHTCORE_PITCH_LOWER + + (float) Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER); + + setTempoSlider(NIGHTCORE_TEMPO); + setPitchSlider(randomPitch); + }); + + resetPresetText = rootView.findViewById(R.id.presetReset); + resetPresetText.setOnClickListener(view -> { + setTempoSlider(DEFAULT_TEMPO); + setPitchSlider(DEFAULT_PITCH); + }); + } + + /*////////////////////////////////////////////////////////////////////////// + // Helper + //////////////////////////////////////////////////////////////////////////*/ + + private void setTempo(final float newTempo) { + if (unhookingCheckbox == null) return; + if (!unhookingCheckbox.isChecked()) { + setSliders(newTempo); + } else { + setTempoSlider(newTempo); + } + } + + private void setPitch(final float newPitch) { + if (unhookingCheckbox == null) return; + if (!unhookingCheckbox.isChecked()) { + setSliders(newPitch); + } else { + setPitchSlider(newPitch); + } + } + + private void setSliders(final float newValue) { + setTempoSlider(newValue); + setPitchSlider(newValue); + } + + private void setTempoSlider(final float newTempo) { + if (tempoSlider == null) return; + // seekbar doesn't register progress if it is the same as the existing progress + tempoSlider.setProgress(Integer.MAX_VALUE); + tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newTempo)); + } + + private void setPitchSlider(final float newPitch) { + if (pitchSlider == null) return; + pitchSlider.setProgress(Integer.MAX_VALUE); + pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newPitch)); + } + + private void setPlaybackParameters(final float tempo, final float pitch) { + if (callback != null && tempoCurrentText != null && pitchCurrentText != null) { + if (DEBUG) Log.d(TAG, "Setting playback parameters to " + + "tempo=[" + tempo + "], " + + "pitch=[" + pitch + "]"); + + tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); + pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); + callback.onPlaybackParameterChanged(tempo, pitch); + } + } + + private float getCurrentTempo() { + return tempoSlider == null ? initialTempo : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, + tempoSlider.getProgress()); + } + + private float getCurrentPitch() { + return pitchSlider == null ? initialPitch : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, + pitchSlider.getProgress()); + } + + /** + * Converts from zeroed float with a minimum offset to the nearest rounded slider + * equivalent integer + * */ + private static int getSliderEquivalent(final float minimumValue, final float floatValue) { + return Math.round((floatValue - minimumValue) * 100f); + } + + /** + * Converts from slider integer value to an equivalent float value with a given minimum offset + * */ + private static float getSliderEquivalent(final float minimumValue, final int intValue) { + return ((float) intValue) / 100f + minimumValue; + } + + private static String getStepUpPercentString(final float percent) { + return STEP_UP_SIGN + PlayerHelper.formatPitch(percent); + } + + private static String getStepDownPercentString(final float percent) { + return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent); + } +} diff --git a/app/src/main/res/layout/dialog_playback_parameter.xml b/app/src/main/res/layout/dialog_playback_parameter.xml new file mode 100644 index 000000000..a8c6a5dcd --- /dev/null +++ b/app/src/main/res/layout/dialog_playback_parameter.xml @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd280ff02..effdeaaba 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -456,4 +456,12 @@ yourid, soundcloud.com/yourid Keep in mind that this operation can be network expensive.\n\nDo you want to continue? + + + Playback Speed Control + Tempo + Pitch + Unhook (may cause distortion) + Nightcore + Default From 8556e992414d074c1d59fea78bebcd8f2624ed61 Mon Sep 17 00:00:00 2001 From: wb9688 Date: Wed, 21 Mar 2018 10:55:22 +0100 Subject: [PATCH 38/69] Remove IP range This would close #1148. I haven't tested it myself yet. @TheAssassin: Could you update the Sentry part? --- .../schabi/newpipe/report/ErrorActivity.java | 45 +------------------ 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java index 8e3509032..017c094aa 100644 --- a/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java +++ b/app/src/main/java/org/schabi/newpipe/report/ErrorActivity.java @@ -77,12 +77,10 @@ public class ErrorActivity extends AppCompatActivity { public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME; - Thread globIpRangeThread; private String[] errorList; private ErrorInfo errorInfo; private Class returnActivity; private String currentTimeStamp; - private String globIpRange; // views private TextView errorView; private EditText userCommentBox; @@ -224,9 +222,6 @@ public class ErrorActivity extends AppCompatActivity { }); reportButton.setEnabled(false); - globIpRangeThread = new Thread(new IpRangeRequester()); - globIpRangeThread.start(); - // normal bugreport buildInfo(errorInfo); if (errorInfo.message != 0) { @@ -342,8 +337,7 @@ public class ErrorActivity extends AppCompatActivity { .put("package", getPackageName()) .put("version", BuildConfig.VERSION_NAME) .put("os", getOsString()) - .put("time", currentTimeStamp) - .put("ip_range", globIpRange); + .put("time", currentTimeStamp); JSONArray exceptionArray = new JSONArray(); if (errorList != null) { @@ -454,41 +448,4 @@ public class ErrorActivity extends AppCompatActivity { dest.writeInt(this.message); } } - - private class IpRangeRequester implements Runnable { - Handler h = new Handler(); - - public void run() { - String ipRange = "none"; - try { - Downloader dl = Downloader.getInstance(); - String ip = dl.download("https://ipv4.icanhazip.com"); - - ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip) - + "0.0"; - } catch (Throwable e) { - Log.w(TAG, "Error while error: could not get iprange", e); - } finally { - h.post(new IpRangeReturnRunnable(ipRange)); - } - } - } - - private class IpRangeReturnRunnable implements Runnable { - String ipRange; - - public IpRangeReturnRunnable(String ipRange) { - this.ipRange = ipRange; - } - - public void run() { - globIpRange = ipRange; - if (infoView != null) { - String text = infoView.getText().toString(); - text += "\n" + globIpRange; - infoView.setText(text); - reportButton.setEnabled(true); - } - } - } } From ae9aa2662a7a9d42310110b8bbf9e7cd6103fb4b Mon Sep 17 00:00:00 2001 From: Tobias Groza Date: Tue, 20 Mar 2018 22:14:33 +0000 Subject: [PATCH 39/69] Translated using Weblate (German) Currently translated at 93.2% (320 of 343 strings) --- app/src/main/res/values-de/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index acb75efd1..fe9de8cab 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -309,7 +309,7 @@ Ausrichtung umschalten In den Hintergrund wechseln Zu Popup wechseln - Zur Hauptseite wechseln + Zum normalen Player wechseln Externe Player unterstützen diese Art von Links nicht Ungültige URL From 18d019c62ae18f9c42a19d98e9cfe7d0cb43fd4a Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Wed, 21 Mar 2018 20:08:33 -0700 Subject: [PATCH 40/69] -Added quadratic slider strategy implementation and tests. -Modified playback speed control to use quadratic sliders instead of linear. -Modified number formatters in player helper to use double instead of float. -Simplified slider behavior in playback parameter dialog. -Fixed potential NPE in base local fragment. --- .../local/bookmark/BaseLocalListFragment.java | 5 +- .../helper/PlaybackParameterDialog.java | 345 ++++++++++-------- .../newpipe/player/helper/PlayerHelper.java | 4 +- .../schabi/newpipe/util/SliderStrategy.java | 73 ++++ .../util/QuadraticSliderStrategyTest.java | 86 +++++ 5 files changed, 353 insertions(+), 160 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java create mode 100644 app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BaseLocalListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BaseLocalListFragment.java index d2c4e1b14..eb366d97f 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BaseLocalListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/local/bookmark/BaseLocalListFragment.java @@ -151,7 +151,10 @@ public abstract class BaseLocalListFragment extends BaseStateFragment @Override public void showListFooter(final boolean show) { - itemsList.post(() -> itemListAdapter.showFooter(show)); + if (itemsList == null) return; + itemsList.post(() -> { + if (itemListAdapter != null) itemListAdapter.showFooter(show); + }); } @Override diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java index 8a0a8a86c..7c7d87791 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlaybackParameterDialog.java @@ -14,59 +14,64 @@ import android.widget.SeekBar; import android.widget.TextView; import org.schabi.newpipe.R; +import org.schabi.newpipe.util.SliderStrategy; import static org.schabi.newpipe.player.BasePlayer.DEBUG; public class PlaybackParameterDialog extends DialogFragment { - private static final String TAG = "PlaybackParameterDialog"; + @NonNull private static final String TAG = "PlaybackParameterDialog"; - public static final float MINIMUM_PLAYBACK_VALUE = 0.25f; - public static final float MAXIMUM_PLAYBACK_VALUE = 3.00f; + public static final double MINIMUM_PLAYBACK_VALUE = 0.25f; + public static final double MAXIMUM_PLAYBACK_VALUE = 3.00f; - public static final String STEP_UP_SIGN = "+"; - public static final String STEP_DOWN_SIGN = "-"; - public static final float PLAYBACK_STEP_VALUE = 0.05f; + public static final char STEP_UP_SIGN = '+'; + public static final char STEP_DOWN_SIGN = '-'; + public static final double PLAYBACK_STEP_VALUE = 0.05f; - public static final float NIGHTCORE_TEMPO = 1.20f; - public static final float NIGHTCORE_PITCH_LOWER = 1.15f; - public static final float NIGHTCORE_PITCH_UPPER = 1.25f; + public static final double NIGHTCORE_TEMPO = 1.20f; + public static final double NIGHTCORE_PITCH_LOWER = 1.15f; + public static final double NIGHTCORE_PITCH_UPPER = 1.25f; - public static final float DEFAULT_TEMPO = 1.00f; - public static final float DEFAULT_PITCH = 1.00f; + public static final double DEFAULT_TEMPO = 1.00f; + public static final double DEFAULT_PITCH = 1.00f; - private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; - private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; + @NonNull private static final String INITIAL_TEMPO_KEY = "initial_tempo_key"; + @NonNull private static final String INITIAL_PITCH_KEY = "initial_pitch_key"; public interface Callback { void onPlaybackParameterChanged(final float playbackTempo, final float playbackPitch); } - private Callback callback; + @Nullable private Callback callback; - private float initialTempo = DEFAULT_TEMPO; - private float initialPitch = DEFAULT_PITCH; + @NonNull private final SliderStrategy strategy = new SliderStrategy.Quadratic( + MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE, + /*centerAt=*/1.00f, /*sliderGranularity=*/10000); - private SeekBar tempoSlider; - private TextView tempoMinimumText; - private TextView tempoMaximumText; - private TextView tempoCurrentText; - private TextView tempoStepDownText; - private TextView tempoStepUpText; + private double initialTempo = DEFAULT_TEMPO; + private double initialPitch = DEFAULT_PITCH; - private SeekBar pitchSlider; - private TextView pitchMinimumText; - private TextView pitchMaximumText; - private TextView pitchCurrentText; - private TextView pitchStepDownText; - private TextView pitchStepUpText; + @Nullable private SeekBar tempoSlider; + @Nullable private TextView tempoMinimumText; + @Nullable private TextView tempoMaximumText; + @Nullable private TextView tempoCurrentText; + @Nullable private TextView tempoStepDownText; + @Nullable private TextView tempoStepUpText; - private CheckBox unhookingCheckbox; + @Nullable private SeekBar pitchSlider; + @Nullable private TextView pitchMinimumText; + @Nullable private TextView pitchMaximumText; + @Nullable private TextView pitchCurrentText; + @Nullable private TextView pitchStepDownText; + @Nullable private TextView pitchStepUpText; - private TextView nightCorePresetText; - private TextView resetPresetText; + @Nullable private CheckBox unhookingCheckbox; - public static PlaybackParameterDialog newInstance(final float playbackTempo, - final float playbackPitch) { + @Nullable private TextView nightCorePresetText; + @Nullable private TextView resetPresetText; + + public static PlaybackParameterDialog newInstance(final double playbackTempo, + final double playbackPitch) { PlaybackParameterDialog dialog = new PlaybackParameterDialog(); dialog.initialTempo = playbackTempo; dialog.initialPitch = playbackPitch; @@ -91,16 +96,16 @@ public class PlaybackParameterDialog extends DialogFragment { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { - initialTempo = savedInstanceState.getFloat(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); - initialPitch = savedInstanceState.getFloat(INITIAL_PITCH_KEY, DEFAULT_PITCH); + initialTempo = savedInstanceState.getDouble(INITIAL_TEMPO_KEY, DEFAULT_TEMPO); + initialPitch = savedInstanceState.getDouble(INITIAL_PITCH_KEY, DEFAULT_PITCH); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putFloat(INITIAL_TEMPO_KEY, initialTempo); - outState.putFloat(INITIAL_PITCH_KEY, initialPitch); + outState.putDouble(INITIAL_TEMPO_KEY, initialTempo); + outState.putDouble(INITIAL_PITCH_KEY, initialPitch); } /*////////////////////////////////////////////////////////////////////////// @@ -111,7 +116,7 @@ public class PlaybackParameterDialog extends DialogFragment { @Override public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { final View view = View.inflate(getContext(), R.layout.dialog_playback_parameter, null); - setupView(view); + setupControlViews(view); final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity()) .setTitle(R.string.playback_speed_control) @@ -120,16 +125,16 @@ public class PlaybackParameterDialog extends DialogFragment { .setNegativeButton(R.string.cancel, (dialogInterface, i) -> setPlaybackParameters(initialTempo, initialPitch)) .setPositiveButton(R.string.finish, (dialogInterface, i) -> - setPlaybackParameters(getCurrentTempo(), getCurrentPitch())); + setCurrentPlaybackParameters()); return dialogBuilder.create(); } /*////////////////////////////////////////////////////////////////////////// - // Dialog Builder + // Control Views //////////////////////////////////////////////////////////////////////////*/ - private void setupView(@NonNull View rootView) { + private void setupControlViews(@NonNull View rootView) { setupHookingControl(rootView); setupTempoControl(rootView); setupPitchControl(rootView); @@ -144,45 +149,34 @@ public class PlaybackParameterDialog extends DialogFragment { tempoStepUpText = rootView.findViewById(R.id.tempoStepUp); tempoStepDownText = rootView.findViewById(R.id.tempoStepDown); - tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo)); - tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); - tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); + if (tempoCurrentText != null) + tempoCurrentText.setText(PlayerHelper.formatSpeed(initialTempo)); + if (tempoMaximumText != null) + tempoMaximumText.setText(PlayerHelper.formatSpeed(MAXIMUM_PLAYBACK_VALUE)); + if (tempoMinimumText != null) + tempoMinimumText.setText(PlayerHelper.formatSpeed(MINIMUM_PLAYBACK_VALUE)); - tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE)); - tempoStepUpText.setOnClickListener(view -> - setTempo(getCurrentTempo() + PLAYBACK_STEP_VALUE)); + if (tempoStepUpText != null) { + tempoStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE)); + tempoStepUpText.setOnClickListener(view -> { + onTempoSliderUpdated(getCurrentTempo() + PLAYBACK_STEP_VALUE); + setCurrentPlaybackParameters(); + }); + } - tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE)); - tempoStepDownText.setOnClickListener(view -> - setTempo(getCurrentTempo() - PLAYBACK_STEP_VALUE)); + if (tempoStepDownText != null) { + tempoStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE)); + tempoStepDownText.setOnClickListener(view -> { + onTempoSliderUpdated(getCurrentTempo() - PLAYBACK_STEP_VALUE); + setCurrentPlaybackParameters(); + }); + } - tempoSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE)); - tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialTempo)); - tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener()); - } - - private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() { - return new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - final float currentTempo = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress); - if (fromUser) { // this change is first in chain - setTempo(currentTempo); - } else { - setPlaybackParameters(currentTempo, getCurrentPitch()); - } - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - // Do Nothing. - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - // Do Nothing. - } - }; + if (tempoSlider != null) { + tempoSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); + tempoSlider.setProgress(strategy.progressOf(initialTempo)); + tempoSlider.setOnSeekBarChangeListener(getOnTempoChangedListener()); + } } private void setupPitchControl(@NonNull View rootView) { @@ -193,32 +187,85 @@ public class PlaybackParameterDialog extends DialogFragment { pitchStepDownText = rootView.findViewById(R.id.pitchStepDown); pitchStepUpText = rootView.findViewById(R.id.pitchStepUp); - pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch)); - pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); - pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); + if (pitchCurrentText != null) + pitchCurrentText.setText(PlayerHelper.formatPitch(initialPitch)); + if (pitchMaximumText != null) + pitchMaximumText.setText(PlayerHelper.formatPitch(MAXIMUM_PLAYBACK_VALUE)); + if (pitchMinimumText != null) + pitchMinimumText.setText(PlayerHelper.formatPitch(MINIMUM_PLAYBACK_VALUE)); - pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE)); - pitchStepUpText.setOnClickListener(view -> - setPitch(getCurrentPitch() + PLAYBACK_STEP_VALUE)); + if (pitchStepUpText != null) { + pitchStepUpText.setText(getStepUpPercentString(PLAYBACK_STEP_VALUE)); + pitchStepUpText.setOnClickListener(view -> { + onPitchSliderUpdated(getCurrentPitch() + PLAYBACK_STEP_VALUE); + setCurrentPlaybackParameters(); + }); + } - pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE)); - pitchStepDownText.setOnClickListener(view -> - setPitch(getCurrentPitch() - PLAYBACK_STEP_VALUE)); + if (pitchStepDownText != null) { + pitchStepDownText.setText(getStepDownPercentString(PLAYBACK_STEP_VALUE)); + pitchStepDownText.setOnClickListener(view -> { + onPitchSliderUpdated(getCurrentPitch() - PLAYBACK_STEP_VALUE); + setCurrentPlaybackParameters(); + }); + } - pitchSlider.setMax(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, MAXIMUM_PLAYBACK_VALUE)); - pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, initialPitch)); - pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); + if (pitchSlider != null) { + pitchSlider.setMax(strategy.progressOf(MAXIMUM_PLAYBACK_VALUE)); + pitchSlider.setProgress(strategy.progressOf(initialPitch)); + pitchSlider.setOnSeekBarChangeListener(getOnPitchChangedListener()); + } } - private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() { + private void setupHookingControl(@NonNull View rootView) { + unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); + if (unhookingCheckbox != null) { + unhookingCheckbox.setChecked(initialPitch != initialTempo); + unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { + if (isChecked) return; + // When unchecked, slide back to the minimum of current tempo or pitch + final double minimum = Math.min(getCurrentPitch(), getCurrentTempo()); + setSliders(minimum); + setCurrentPlaybackParameters(); + }); + } + } + + private void setupPresetControl(@NonNull View rootView) { + nightCorePresetText = rootView.findViewById(R.id.presetNightcore); + if (nightCorePresetText != null) { + nightCorePresetText.setOnClickListener(view -> { + final double randomPitch = NIGHTCORE_PITCH_LOWER + + Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER); + + setTempoSlider(NIGHTCORE_TEMPO); + setPitchSlider(randomPitch); + setCurrentPlaybackParameters(); + }); + } + + resetPresetText = rootView.findViewById(R.id.presetReset); + if (resetPresetText != null) { + resetPresetText.setOnClickListener(view -> { + setTempoSlider(DEFAULT_TEMPO); + setPitchSlider(DEFAULT_PITCH); + setCurrentPlaybackParameters(); + }); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Sliders + //////////////////////////////////////////////////////////////////////////*/ + + private SeekBar.OnSeekBarChangeListener getOnTempoChangedListener() { return new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - final float currentPitch = getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, progress); - if (fromUser) { // this change is first in chain - setPitch(currentPitch); - } else { - setPlaybackParameters(getCurrentTempo(), currentPitch); + final double currentTempo = strategy.valueOf(progress); + if (fromUser) { + onTempoSliderUpdated(currentTempo); + setCurrentPlaybackParameters(); } } @@ -234,38 +281,30 @@ public class PlaybackParameterDialog extends DialogFragment { }; } - private void setupHookingControl(@NonNull View rootView) { - unhookingCheckbox = rootView.findViewById(R.id.unhookCheckbox); - unhookingCheckbox.setOnCheckedChangeListener((compoundButton, isChecked) -> { - if (isChecked) return; - // When unchecked, slide back to the minimum of current tempo or pitch - final float minimum = Math.min(getCurrentPitch(), getCurrentTempo()); - setSliders(minimum); - }); + private SeekBar.OnSeekBarChangeListener getOnPitchChangedListener() { + return new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + final double currentPitch = strategy.valueOf(progress); + if (fromUser) { // this change is first in chain + onPitchSliderUpdated(currentPitch); + setCurrentPlaybackParameters(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + // Do Nothing. + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + // Do Nothing. + } + }; } - private void setupPresetControl(@NonNull View rootView) { - nightCorePresetText = rootView.findViewById(R.id.presetNightcore); - nightCorePresetText.setOnClickListener(view -> { - final float randomPitch = NIGHTCORE_PITCH_LOWER + - (float) Math.random() * (NIGHTCORE_PITCH_UPPER - NIGHTCORE_PITCH_LOWER); - - setTempoSlider(NIGHTCORE_TEMPO); - setPitchSlider(randomPitch); - }); - - resetPresetText = rootView.findViewById(R.id.presetReset); - resetPresetText.setOnClickListener(view -> { - setTempoSlider(DEFAULT_TEMPO); - setPitchSlider(DEFAULT_PITCH); - }); - } - - /*////////////////////////////////////////////////////////////////////////// - // Helper - //////////////////////////////////////////////////////////////////////////*/ - - private void setTempo(final float newTempo) { + private void onTempoSliderUpdated(final double newTempo) { if (unhookingCheckbox == null) return; if (!unhookingCheckbox.isChecked()) { setSliders(newTempo); @@ -274,7 +313,7 @@ public class PlaybackParameterDialog extends DialogFragment { } } - private void setPitch(final float newPitch) { + private void onPitchSliderUpdated(final double newPitch) { if (unhookingCheckbox == null) return; if (!unhookingCheckbox.isChecked()) { setSliders(newPitch); @@ -283,25 +322,30 @@ public class PlaybackParameterDialog extends DialogFragment { } } - private void setSliders(final float newValue) { + private void setSliders(final double newValue) { setTempoSlider(newValue); setPitchSlider(newValue); } - private void setTempoSlider(final float newTempo) { + private void setTempoSlider(final double newTempo) { if (tempoSlider == null) return; - // seekbar doesn't register progress if it is the same as the existing progress - tempoSlider.setProgress(Integer.MAX_VALUE); - tempoSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newTempo)); + tempoSlider.setProgress(strategy.progressOf(newTempo)); } - private void setPitchSlider(final float newPitch) { + private void setPitchSlider(final double newPitch) { if (pitchSlider == null) return; - pitchSlider.setProgress(Integer.MAX_VALUE); - pitchSlider.setProgress(getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, newPitch)); + pitchSlider.setProgress(strategy.progressOf(newPitch)); } - private void setPlaybackParameters(final float tempo, final float pitch) { + /*////////////////////////////////////////////////////////////////////////// + // Helper + //////////////////////////////////////////////////////////////////////////*/ + + private void setCurrentPlaybackParameters() { + setPlaybackParameters(getCurrentTempo(), getCurrentPitch()); + } + + private void setPlaybackParameters(final double tempo, final double pitch) { if (callback != null && tempoCurrentText != null && pitchCurrentText != null) { if (DEBUG) Log.d(TAG, "Setting playback parameters to " + "tempo=[" + tempo + "], " + @@ -309,40 +353,27 @@ public class PlaybackParameterDialog extends DialogFragment { tempoCurrentText.setText(PlayerHelper.formatSpeed(tempo)); pitchCurrentText.setText(PlayerHelper.formatPitch(pitch)); - callback.onPlaybackParameterChanged(tempo, pitch); + callback.onPlaybackParameterChanged((float) tempo, (float) pitch); } } - private float getCurrentTempo() { - return tempoSlider == null ? initialTempo : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, + private double getCurrentTempo() { + return tempoSlider == null ? initialTempo : strategy.valueOf( tempoSlider.getProgress()); } - private float getCurrentPitch() { - return pitchSlider == null ? initialPitch : getSliderEquivalent(MINIMUM_PLAYBACK_VALUE, + private double getCurrentPitch() { + return pitchSlider == null ? initialPitch : strategy.valueOf( pitchSlider.getProgress()); } - /** - * Converts from zeroed float with a minimum offset to the nearest rounded slider - * equivalent integer - * */ - private static int getSliderEquivalent(final float minimumValue, final float floatValue) { - return Math.round((floatValue - minimumValue) * 100f); - } - - /** - * Converts from slider integer value to an equivalent float value with a given minimum offset - * */ - private static float getSliderEquivalent(final float minimumValue, final int intValue) { - return ((float) intValue) / 100f + minimumValue; - } - - private static String getStepUpPercentString(final float percent) { + @NonNull + private static String getStepUpPercentString(final double percent) { return STEP_UP_SIGN + PlayerHelper.formatPitch(percent); } - private static String getStepDownPercentString(final float percent) { + @NonNull + private static String getStepDownPercentString(final double percent) { return STEP_DOWN_SIGN + PlayerHelper.formatPitch(percent); } } diff --git a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java index b34cec724..63ac7e8a1 100644 --- a/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java +++ b/app/src/main/java/org/schabi/newpipe/player/helper/PlayerHelper.java @@ -60,11 +60,11 @@ public class PlayerHelper { : stringFormatter.format("%02d:%02d", minutes, seconds).toString(); } - public static String formatSpeed(float speed) { + public static String formatSpeed(double speed) { return speedFormatter.format(speed); } - public static String formatPitch(float pitch) { + public static String formatPitch(double pitch) { return pitchFormatter.format(pitch); } diff --git a/app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java b/app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java new file mode 100644 index 000000000..efec1abb0 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/SliderStrategy.java @@ -0,0 +1,73 @@ +package org.schabi.newpipe.util; + +public interface SliderStrategy { + /** + * Converts from zeroed double with a minimum offset to the nearest rounded slider + * equivalent integer + * */ + int progressOf(final double value); + + /** + * Converts from slider integer value to an equivalent double value with a given + * minimum offset + * */ + double valueOf(final int progress); + + // TODO: also implement linear strategy when needed + + final class Quadratic implements SliderStrategy { + private final double leftGap; + private final double rightGap; + private final double center; + + private final int centerProgress; + + /** + * Quadratic slider strategy that scales the value of a slider given how far the slider + * progress is from the center of the slider. The further away from the center, + * the faster the interpreted value changes, and vice versa. + * + * @param minimum the minimum value of the interpreted value of the slider. + * @param maximum the maximum value of the interpreted value of the slider. + * @param center center of the interpreted value between the minimum and maximum, which + * will be used as the center value on the slider progress. Doesn't need + * to be the average of the minimum and maximum values, but must be in + * between the two. + * @param maxProgress the maximum possible progress of the slider, this is the + * value that is shown for the UI and controls the granularity of + * the slider. Should be as large as possible to avoid floating + * point round-off error. Using odd number is recommended. + * */ + public Quadratic(double minimum, double maximum, double center, int maxProgress) { + if (center < minimum || center > maximum) { + throw new IllegalArgumentException("Center must be in between minimum and maximum"); + } + + this.leftGap = minimum - center; + this.rightGap = maximum - center; + this.center = center; + + this.centerProgress = maxProgress / 2; + } + + @Override + public int progressOf(double value) { + final double difference = value - center; + final double root = difference >= 0 ? + Math.sqrt(difference / rightGap) : + -Math.sqrt(Math.abs(difference / leftGap)); + final double offset = Math.round(root * centerProgress); + + return (int) (centerProgress + offset); + } + + @Override + public double valueOf(int progress) { + final int offset = progress - centerProgress; + final double square = Math.pow(((double) offset) / ((double) centerProgress), 2); + final double difference = square * (offset >= 0 ? rightGap : leftGap); + + return difference + center; + } + } +} diff --git a/app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java b/app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java new file mode 100644 index 000000000..8c8d52043 --- /dev/null +++ b/app/src/test/java/org/schabi/newpipe/util/QuadraticSliderStrategyTest.java @@ -0,0 +1,86 @@ +package org.schabi.newpipe.util; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class QuadraticSliderStrategyTest { + private final static int STEP = 100; + private final static float DELTA = 1f / (float) STEP; + + private final SliderStrategy.Quadratic standard = + new SliderStrategy.Quadratic(0f, 100f, 50f, STEP); + @Test + public void testLeftBound() throws Exception { + assertEquals(standard.progressOf(0), 0); + assertEquals(standard.valueOf(0), 0f, DELTA); + } + + @Test + public void testCenter() throws Exception { + assertEquals(standard.progressOf(50), 50); + assertEquals(standard.valueOf(50), 50f, DELTA); + } + + @Test + public void testRightBound() throws Exception { + assertEquals(standard.progressOf(100), 100); + assertEquals(standard.valueOf(100), 100f, DELTA); + } + + @Test + public void testLeftRegion() throws Exception { + final int leftProgress = standard.progressOf(25); + final double leftValue = standard.valueOf(25); + assertTrue(leftProgress > 0 && leftProgress < 50); + assertTrue(leftValue > 0f && leftValue < 50); + } + + @Test + public void testRightRegion() throws Exception { + final int leftProgress = standard.progressOf(75); + final double leftValue = standard.valueOf(75); + assertTrue(leftProgress > 50 && leftProgress < 100); + assertTrue(leftValue > 50f && leftValue < 100); + } + + @Test + public void testConversion() throws Exception { + assertEquals(standard.progressOf(standard.valueOf(0)), 0); + assertEquals(standard.progressOf(standard.valueOf(25)), 25); + assertEquals(standard.progressOf(standard.valueOf(50)), 50); + assertEquals(standard.progressOf(standard.valueOf(75)), 75); + assertEquals(standard.progressOf(standard.valueOf(100)), 100); + } + + @Test + public void testReverseConversion() throws Exception { + // Need a larger delta since step size / granularity is too small and causes + // floating point round-off errors during conversion + final float largeDelta = 1f; + + assertEquals(standard.valueOf(standard.progressOf(0)), 0f, largeDelta); + assertEquals(standard.valueOf(standard.progressOf(25)), 25f, largeDelta); + assertEquals(standard.valueOf(standard.progressOf(50)), 50f, largeDelta); + assertEquals(standard.valueOf(standard.progressOf(75)), 75f, largeDelta); + assertEquals(standard.valueOf(standard.progressOf(100)), 100f, largeDelta); + } + + @Test + public void testQuadraticPropertyLeftRegion() throws Exception { + final double differenceCloserToCenter = + Math.abs(standard.valueOf(40) - standard.valueOf(45)); + final double differenceFurtherFromCenter = + Math.abs(standard.valueOf(10) - standard.valueOf(15)); + assertTrue(differenceCloserToCenter < differenceFurtherFromCenter); + } + + @Test + public void testQuadraticPropertyRightRegion() throws Exception { + final double differenceCloserToCenter = + Math.abs(standard.valueOf(75) - standard.valueOf(70)); + final double differenceFurtherFromCenter = + Math.abs(standard.valueOf(95) - standard.valueOf(90)); + assertTrue(differenceCloserToCenter < differenceFurtherFromCenter); + } +} From 8b60397f068c2d99f0d92d796400c6f77c2e5cd6 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 22 Mar 2018 18:11:59 -0700 Subject: [PATCH 41/69] -Changed detail fragment thumbnail failure to produce a snackbar error rather than a full error activity. --- .../fragments/detail/VideoDetailFragment.java | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 2a95125df..74e561f99 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -43,6 +43,7 @@ import android.widget.Toast; import com.nirhart.parallaxscroll.views.ParallaxScrollView; import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import org.schabi.newpipe.R; @@ -582,27 +583,20 @@ public class VideoDetailFragment }; } - private void initThumbnailViews(StreamInfo info) { + private void initThumbnailViews(@NonNull StreamInfo info) { thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); if (!TextUtils.isEmpty(info.getThumbnailUrl())) { - imageLoader.displayImage( - info.getThumbnailUrl(), - thumbnailImageView, - ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, - new SimpleImageLoadingListener() { + final String infoServiceName = NewPipe.getNameOfService(info.getServiceId()); + final ImageLoadingListener onFailListener = new SimpleImageLoadingListener() { @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { - ErrorActivity.reportError( - activity, - failReason.getCause(), - null, - activity.findViewById(android.R.id.content), - ErrorActivity.ErrorInfo.make(UserAction.LOAD_IMAGE, - NewPipe.getNameOfService(currentInfo.getServiceId()), - imageUri, - R.string.could_not_load_thumbnails)); + showSnackBarError(failReason.getCause(), UserAction.LOAD_IMAGE, + infoServiceName, imageUri, R.string.could_not_load_thumbnails); } - }); + }; + + imageLoader.displayImage(info.getThumbnailUrl(), thumbnailImageView, + ImageDisplayConstants.DISPLAY_THUMBNAIL_OPTIONS, onFailListener); } if (!TextUtils.isEmpty(info.getUploaderAvatarUrl())) { From 72eaff148cbb473c840e59f4dd339c3c6c73abf2 Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 22 Mar 2018 18:12:11 -0700 Subject: [PATCH 42/69] -Fixed main player paused video not abandoning audio focus after navigating away from activity during interruption, when resume on focus regain is enabled. -Separated onPause and onPlay functions from onPlayPause. -Renamed onVideoPlayPause to onPlayPause. --- .../newpipe/player/BackgroundPlayer.java | 2 +- .../org/schabi/newpipe/player/BasePlayer.java | 33 ++++++++++++++----- .../newpipe/player/MainVideoPlayer.java | 7 ++-- .../newpipe/player/PopupVideoPlayer.java | 4 +-- .../newpipe/player/ServicePlayerActivity.java | 2 +- .../playback/BasePlayerMediaSession.java | 4 +-- 6 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java index f799941bd..ac070fb44 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java @@ -486,7 +486,7 @@ public final class BackgroundPlayer extends Service { onClose(); break; case ACTION_PLAY_PAUSE: - onVideoPlayPause(); + onPlayPause(); break; case ACTION_REPEAT: onRepeatClicked(); diff --git a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java index fe19030f9..cd1451d37 100644 --- a/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/BasePlayer.java @@ -392,7 +392,7 @@ public abstract class BasePlayer implements if (intent == null || intent.getAction() == null) return; switch (intent.getAction()) { case AudioManager.ACTION_AUDIO_BECOMING_NOISY: - if (isPlaying()) onVideoPlayPause(); + onPause(); break; } } @@ -948,14 +948,11 @@ public abstract class BasePlayer implements changeState(playWhenReady ? STATE_PLAYING : STATE_PAUSED); } - public void onVideoPlayPause() { - if (DEBUG) Log.d(TAG, "onVideoPlayPause() called"); + public void onPlay() { + if (DEBUG) Log.d(TAG, "onPlay() called"); + if (audioReactor == null || playQueue == null || simpleExoPlayer == null) return; - if (!isPlaying()) { - audioReactor.requestAudioFocus(); - } else { - audioReactor.abandonAudioFocus(); - } + audioReactor.requestAudioFocus(); if (getCurrentState() == STATE_COMPLETED) { if (playQueue.getIndex() == 0) { @@ -965,7 +962,25 @@ public abstract class BasePlayer implements } } - simpleExoPlayer.setPlayWhenReady(!isPlaying()); + simpleExoPlayer.setPlayWhenReady(true); + } + + public void onPause() { + if (DEBUG) Log.d(TAG, "onPause() called"); + if (audioReactor == null || simpleExoPlayer == null) return; + + audioReactor.abandonAudioFocus(); + simpleExoPlayer.setPlayWhenReady(false); + } + + public void onPlayPause() { + if (DEBUG) Log.d(TAG, "onPlayPause() called"); + + if (!isPlaying()) { + onPlay(); + } else { + onPause(); + } } public void onFastRewind() { diff --git a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java index cbc4b8230..dbc34b11a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/MainVideoPlayer.java @@ -49,7 +49,6 @@ import android.widget.SeekBar; import android.widget.TextView; import android.widget.Toast; -import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; import com.google.android.exoplayer2.ui.SubtitleView; @@ -153,7 +152,7 @@ public final class MainVideoPlayer extends AppCompatActivity if (DEBUG) Log.d(TAG, "onResume() called"); if (playerImpl.getPlayer() != null && activityPaused && playerImpl.wasPlaying() && !playerImpl.isPlaying()) { - playerImpl.onVideoPlayPause(); + playerImpl.onPlay(); } activityPaused = false; @@ -188,7 +187,7 @@ public final class MainVideoPlayer extends AppCompatActivity if (playerImpl != null && playerImpl.getPlayer() != null && !activityPaused) { playerImpl.wasPlaying = playerImpl.isPlaying(); - if (playerImpl.isPlaying()) playerImpl.onVideoPlayPause(); + playerImpl.onPause(); } activityPaused = true; } @@ -563,7 +562,7 @@ public final class MainVideoPlayer extends AppCompatActivity public void onClick(View v) { super.onClick(v); if (v.getId() == playPauseButton.getId()) { - onVideoPlayPause(); + onPlayPause(); } else if (v.getId() == playPreviousButton.getId()) { onPlayPrevious(); diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java index 64dc03da6..20860d9c5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -618,7 +618,7 @@ public final class PopupVideoPlayer extends Service { onClose(); break; case ACTION_PLAY_PAUSE: - onVideoPlayPause(); + onPlayPause(); break; case ACTION_REPEAT: onRepeatClicked(); @@ -731,7 +731,7 @@ public final class PopupVideoPlayer extends Service { public boolean onSingleTapConfirmed(MotionEvent e) { if (DEBUG) Log.d(TAG, "onSingleTapConfirmed() called with: e = [" + e + "]"); if (playerImpl == null || playerImpl.getPlayer() == null) return false; - playerImpl.onVideoPlayPause(); + playerImpl.onPlayPause(); return true; } diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 1f850944d..994aa60e8 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -424,7 +424,7 @@ public abstract class ServicePlayerActivity extends AppCompatActivity player.onPlayPrevious(); } else if (view.getId() == playPauseButton.getId()) { - player.onVideoPlayPause(); + player.onPlayPause(); } else if (view.getId() == forwardButton.getId()) { player.onPlayNext(); diff --git a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java index 07504542c..616879917 100644 --- a/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java +++ b/app/src/main/java/org/schabi/newpipe/player/playback/BasePlayerMediaSession.java @@ -62,12 +62,12 @@ public class BasePlayerMediaSession implements MediaSessionCallback { @Override public void onPlay() { - if (!player.isPlaying()) player.onVideoPlayPause(); + player.onPlay(); } @Override public void onPause() { - if (player.isPlaying()) player.onVideoPlayPause(); + player.onPause(); } @Override From 02f48ccc7f4e0338557a47bc24d0993e2b4a71ff Mon Sep 17 00:00:00 2001 From: John Zhen Mo Date: Thu, 22 Mar 2018 18:44:03 -0700 Subject: [PATCH 43/69] -Removed duplicate dialog open instances in service player activity. --- .../newpipe/player/ServicePlayerActivity.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java index 994aa60e8..239c9c8d3 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java +++ b/app/src/main/java/org/schabi/newpipe/player/ServicePlayerActivity.java @@ -433,12 +433,10 @@ public abstract class ServicePlayerActivity extends AppCompatActivity player.onShuffleClicked(); } else if (view.getId() == playbackSpeedButton.getId()) { - PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), - player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag()); + openPlaybackParameterDialog(); } else if (view.getId() == playbackPitchButton.getId()) { - PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), - player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag()); + openPlaybackParameterDialog(); } else if (view.getId() == metadata.getId()) { scrollToSelected(); @@ -450,9 +448,15 @@ public abstract class ServicePlayerActivity extends AppCompatActivity } //////////////////////////////////////////////////////////////////////////// - // Playback Parameters Listener + // Playback Parameters //////////////////////////////////////////////////////////////////////////// + private void openPlaybackParameterDialog() { + if (player == null) return; + PlaybackParameterDialog.newInstance(player.getPlaybackSpeed(), + player.getPlaybackPitch()).show(getSupportFragmentManager(), getTag()); + } + @Override public void onPlaybackParameterChanged(float playbackTempo, float playbackPitch) { if (player != null) player.setPlaybackParameters(playbackTempo, playbackPitch); From 40ea5eb53d375a6615d0a255703908c8f15d7a83 Mon Sep 17 00:00:00 2001 From: TobiGr Date: Fri, 23 Mar 2018 16:15:50 +0100 Subject: [PATCH 44/69] Update README.md Add compressed screenshots Add liberapay to donation section --- README.md | 27 +++++++++++------- assets/bitcoin_qr_code.png | Bin 346 -> 784 bytes assets/bountysource_qr_code.png | Bin 1269 -> 801 bytes assets/liberapay_donate_button.svg | 1 + assets/liberapay_qr_code.png | Bin 0 -> 807 bytes .../en-US/images/phoneScreenshots/shot_1.png | Bin 280512 -> 66176 bytes .../en-US/images/phoneScreenshots/shot_2.png | Bin 97633 -> 30026 bytes .../en-US/images/phoneScreenshots/shot_3.png | Bin 157863 -> 29447 bytes .../en-US/images/phoneScreenshots/shot_4.png | Bin 285126 -> 91922 bytes .../en-US/images/phoneScreenshots/shot_5.png | Bin 410030 -> 50829 bytes .../en-US/images/phoneScreenshots/shot_7.png | Bin 142697 -> 102615 bytes .../en-US/images/phoneScreenshots/shot_8.png | Bin 51055 -> 45961 bytes .../en-US/images/phoneScreenshots/shot_9.png | Bin 211855 -> 19404 bytes 13 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 assets/liberapay_donate_button.svg create mode 100644 assets/liberapay_qr_code.png diff --git a/README.md b/README.md index 52c1159dd..065e35bf1 100644 --- a/README.md +++ b/README.md @@ -77,19 +77,24 @@ The more is done the better it gets! If you'd like to get involved, check our [contribution notes](.github/CONTRIBUTING.md). ## Donate -If you like NewPipe we'd be happy about a donation. You can either donate via Bitcoin or BountySource. For further information about donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate/). +If you like NewPipe we'd be happy about a donation. You can either donate via Bitcoin, Bountysource or Liberapay. For further information about donating to NewPipe, please visit our [website](https://newpipe.schabi.org/donate/). - - - - - - - - - - + + + + + + + + + + + + + + +
BitcoinBitcoin QR Code16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
Visit NewPipe at bountysource.comCheck out how many bounties you can earn.
BitcoinBitcoin QR Code16A9J59ahMRqkLSZjhYj33n9j3fMztFxnh
LiberapayVisit NewPipe at liberapay.comDonate via Liberapay
BountysourceVisit NewPipe at bountysource.comCheck out how many bounties you can earn.
## License diff --git a/assets/bitcoin_qr_code.png b/assets/bitcoin_qr_code.png index 17fa26518dbbce2349e78b4c1aae4df3910ee0cb..13e3f76fecac1e5c519a3542e7cad9a7c299ea09 100644 GIT binary patch delta 772 zcmcb`G=Xh`L_G%^0|P_Bqp2Yb3{2NOT^vIy=DeNm*muW)hxz&c`R`(iJRZE2yR;}r zQIl!k4WpxJy2oyucUt_V(wgB{G=rpC1COvn8>hnIEv%n@);yZN>Gavjr|cKDZ)ISb zkiYwm&B{N*RT17SK^M<0-`x{3@12}pacJ{yM}|!^S4-XDWtwodeopp-X@MU-Mep-& zxO6!6el$?-=JzmxhMaG{96=vTxWj7hy-L(=U^t@5rohiucskfI^Q zBGa^PpM7?2N!eOffrdXN@(T55G}%^qb6WhYbLXFaZ#zH3Cl4gL-(UJ^TWNPlec4J? zTg?WBC*PAA{%HJ`-ZXpVj~(+?#-553PZPCp*!PB&!(mUu-Or*8qBHJFUddtLtYBJV z@IFnNf63w{eL;+<9o#uI`6c?HUUPEHN4neuyG|D44_0t&m})Zy5m zpK2u{&))gQ=J|bn!@$Vl(D%IFNA$s}#XWto@9wSQcVJk=&fbx*XLevsOnv$E-P?cf zVKDq&HeX!f*}Zq4-5NDk>G+F=&0L?dBE_z(s^*s~!_MhHZ@m2YQk?Ng+_U86$Cvk} z?lFBoFZ|}~Uoz#p8JCOlOh{y9KC&m~{1hv$)fKCD1EXbgy4Qc138&s?uqLcJF{M6T z%ie)uk|m3S(<{BJd*44Rd|JiCqQKyJ=J-BE*{3`I`KNh&We{j!P+?jU?d9bE?*|9q3mE#tx|qaRDDzI=s-^^ohOnOR4xSCZI2?|63}& zbK1VA?@_SiJCQeEHi$W_;Qcu19lR@f zNA4PvM>gAbU~0NF>)_kOwc`JS{bfTcw=*eDbr%H&)_ryljht0HsrSyVKcoFU_f&$y91cq89ZJ6T-G@yGywoCT3qq~ delta 331 zcmV-R0kr;*2HFCU7=Hl(00013nW*^y00ALML_t(|+O^a>ZiFxp2H+pLa0eH#Je1Xv>>79p z=-1hjj)0!~wv1#6+=)iCzH+z_ryk9wO+#Zyr101;dNmt*&9!$k&=@-AWz@=fwsg#3 zf{tn!#88%altfLZ#v3}eN?TEot0~Uji_d#k()^loEY@u3 dKmLk8^Z~+~^P3w}ZZiM?002ovPDHLkV1mp-llcGu diff --git a/assets/bountysource_qr_code.png b/assets/bountysource_qr_code.png index 4fe03236a7fdbfcae026a6421adde61744fab141..18ff10fd041d94baf04714897d26fa64aacfe00d 100644 GIT binary patch literal 801 zcmV++1K#|JP)GW9u9k7J^;ugdpPX<^y#1+fT{XB3!4@| z0P-^!Apey09irAdeO5J&2Q_>2-#Z;1;~95YWCJbp9a#YQA4NZI-1C$)>Hvx zG>ckO1(4A!YE4!1&t7K%ouJNAlgmh)bu$7a(0^4A5Thn__EjLOA?p3k+=)ID86BTh z0EuUGd{zM@p3(7Hb>qjy9hN%bK?UOUKlR#5)EMrVRr>&>G2Ah$_5p}tBz1oppaaK& zcBpHm6Ndogy#J~1RyuJAK+gN0`gRPeXFHl+hO@jUUMPTE)TizP#PG8vkdE0Wjdwt) zmzr0tQD;>FM9Qny0OVVMq)erj;XqU8re46V2Y_761&G+QGwas@=?qUvU4Xiz&Rn%m z(hoo`>Hz`}&ESMtM=77HddL5rvjDlM2Z*Sz0kUeGQs=W){XABCrgEu%PJ1~69RXy~ z%NghhAd6npLsAP+`>0@@D=B~g1R$q5qurFj!^?199H-s|2#|0+KpOp3KTiD^kkrCD z##Ij~IX>010BHyyji7EIMgJ`jM=7&AydbK7fB-}@?wAFLI7AJqLU_`r1J)HlgnEDg zWHxALRw{G~xO&#j0uX?l&jQHsK(&vv47I=N!C5vy00NL#fT(i7I-n+AoX&l<=5Qw2 zOz8uV#hGL?r4K+BXHwx(3rL2$I;+BWGIsp~1R&>2>iALhD}bPP1dz=Hkj(^;%>me8wfzOJ0!O5W~h69W`8#%-pn^hudQ zdgeR;RQ|%uqA%(ykI;wUh4wcj;7S<_tUdT)@RczDC?0ATpE#v@81c>bE!8|PnYV6G z)OYEg4#^=~Gy8P7`FGW|g`c0>dk(?)j)8jeVtGqoz*#i7cMn)xSz0xFUat#SrzZPb za4*nG^v6@v4nLVPQ?LovT?qsZ9No{YQRJT79gHrB=Ah=}8vi{FN3$72>{Lj-8V~jo zh~+>P|3*G0^MX6qP3}Q)C+d(3^xY8Tu^6)kSJWu4<*LM6nR9U!Ii7%$}@Nj#Fu~NCt5UyCV_03)G9^-|AN`dm6_0Tv^Z|{V7F2AUvBSAESqc`8g9F= zqegJd0E!@6KdyNib%@f*KgmpgZN8=xxm=zG!|4gF+9?!K69()Sdpp6hyDHXwZ!I`RaO= zMERPKHKv16e9h;@^g2h{PHjr)AUTgP_X5)><}zQ`Wk1juco4MgP}8Q^p@s196euS% zArIo6q64XyLU;hhS3LyHC1-xyZu`0GCw0Pvl)%hg4tv}lL}SNJ$>CI%SN&iA1l~<) z*Q3~g8F@52d)MT}Ohls$X1xGWBL$`mv8BkPZ;0^X>IO{J7D=|M(i6ufqM7Z2X6J&HxLq|tv|ixYk_JDJTEbigNJmr`z7g#3|(u%FDt z792ELv0YABX>q4-#O#J7l-j6TrxpXSeN=QLegPjM#guY5O{g-E$iob{Wpuk>n?$!Lv%^r;~;@ zKV;hsVTnS(SinboyxdLTe@tV4n=POW);o7eSM@}5QGTnUHUAytyS;M*