Initial commit
This commit is contained in:
		
							
								
								
									
										40
									
								
								app/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								app/build.gradle
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| apply plugin: 'com.android.application' | ||||
|  | ||||
| apply plugin: 'kotlin-android' | ||||
|  | ||||
| apply plugin: 'kotlin-android-extensions' | ||||
|  | ||||
| android { | ||||
|     compileSdkVersion 28 | ||||
|     buildToolsVersion "29.0.2" | ||||
|     defaultConfig { | ||||
|         applicationId "zoo.wbooster" | ||||
|         minSdkVersion 24 | ||||
|         targetSdkVersion 28 | ||||
|         versionCode 1 | ||||
|         versionName "1.0" | ||||
|         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
|     } | ||||
|     buildTypes { | ||||
|         release { | ||||
|             minifyEnabled false | ||||
|             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation fileTree(dir: 'libs', include: ['*.jar']) | ||||
|     implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | ||||
|     implementation 'androidx.appcompat:appcompat:1.0.2' | ||||
|     implementation 'androidx.core:core-ktx:1.0.2' | ||||
|     implementation 'androidx.legacy:legacy-support-v4:1.0.0' | ||||
|     implementation 'com.github.yausername.youtubedl-android:library:0.6.+' | ||||
|     implementation 'com.github.yausername.youtubedl-android:ffmpeg:0.6.+' | ||||
|     implementation 'com.beust:klaxon:5.0.1' | ||||
|     implementation 'com.vdurmont:emoji-java:5.1.1' | ||||
|     testImplementation 'junit:junit:4.12' | ||||
|     androidTestImplementation 'androidx.test.ext:junit:1.1.0' | ||||
|     androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:1.1.3' | ||||
| } | ||||
							
								
								
									
										21
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/proguard-rules.pro
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| # Add project specific ProGuard rules here. | ||||
| # You can control the set of applied configuration files using the | ||||
| # proguardFiles setting in build.gradle. | ||||
| # | ||||
| # For more details, see | ||||
| #   http://developer.android.com/guide/developing/tools/proguard.html | ||||
|  | ||||
| # If your project uses WebView with JS, uncomment the following | ||||
| # and specify the fully qualified class name to the JavaScript interface | ||||
| # class: | ||||
| #-keepclassmembers class fqcn.of.javascript.interface.for.webview { | ||||
| #   public *; | ||||
| #} | ||||
|  | ||||
| # Uncomment this to preserve the line number information for | ||||
| # debugging stack traces. | ||||
| #-keepattributes SourceFile,LineNumberTable | ||||
|  | ||||
| # If you keep the line number information, uncomment this to | ||||
| # hide the original source file name. | ||||
| #-renamesourcefileattribute SourceFile | ||||
| @@ -0,0 +1,24 @@ | ||||
| package zoo.wbooster | ||||
|  | ||||
| import androidx.test.platform.app.InstrumentationRegistry | ||||
| import androidx.test.ext.junit.runners.AndroidJUnit4 | ||||
|  | ||||
| import org.junit.Test | ||||
| import org.junit.runner.RunWith | ||||
|  | ||||
| import org.junit.Assert.* | ||||
|  | ||||
| /** | ||||
|  * Instrumented test, which will execute on an Android device. | ||||
|  * | ||||
|  * See [testing documentation](http://d.android.com/tools/testing). | ||||
|  */ | ||||
| @RunWith(AndroidJUnit4::class) | ||||
| class ExampleInstrumentedTest { | ||||
|     @Test | ||||
|     fun useAppContext() { | ||||
|         // Context of the app under test. | ||||
|         val appContext = InstrumentationRegistry.getInstrumentation().targetContext | ||||
|         assertEquals("zoo.wbooster", appContext.packageName) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								app/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								app/src/main/AndroidManifest.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     package="zoo.wbooster"> | ||||
|  | ||||
|     <uses-permission android:name="android.permission.INTERNET"/> | ||||
|     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> | ||||
|  | ||||
|     <application | ||||
|         android:allowBackup="true" | ||||
|         android:icon="@mipmap/ic_launcher" | ||||
|         android:label="@string/app_name" | ||||
|         android:roundIcon="@mipmap/ic_launcher" | ||||
|         android:supportsRtl="true" | ||||
|         android:theme="@style/AppTheme"> | ||||
|         <service android:name=".MediaPlayerService"/> | ||||
|         <service android:name=".DownloaderService"/> | ||||
|         <activity | ||||
|             android:name=".FullscreenActivity" | ||||
|             android:configChanges="orientation|keyboardHidden|screenSize" | ||||
|             android:label="@string/app_name" | ||||
|             android:theme="@style/FullscreenTheme"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|  | ||||
|                 <category android:name="android.intent.category.LAUNCHER" /> | ||||
|             </intent-filter> | ||||
|         </activity> | ||||
|     </application> | ||||
|  | ||||
| </manifest> | ||||
							
								
								
									
										124
									
								
								app/src/main/java/zoo/wbooster/FullscreenActivity.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								app/src/main/java/zoo/wbooster/FullscreenActivity.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| package zoo.wbooster | ||||
|  | ||||
| import android.content.ComponentName | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
| import android.content.ServiceConnection | ||||
| import android.net.Uri | ||||
| import android.os.Build | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import android.os.Bundle | ||||
| import android.os.IBinder | ||||
| import android.view.View | ||||
| import androidx.annotation.RequiresApi | ||||
| import androidx.core.content.ContextCompat | ||||
| import kotlinx.android.synthetic.main.activity_fullscreen.* | ||||
| import java.net.URI | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Full screen activity devoted to play music | ||||
|  */ | ||||
| class FullscreenActivity : AppCompatActivity() { | ||||
|  | ||||
|     private lateinit var mediaPlayerService: MediaPlayerService | ||||
|     private lateinit var downloaderService: DownloaderService | ||||
|     private var isDownloaderServiceBound: Boolean = false | ||||
|     private var isMediaPlayerServiceBound: Boolean = false | ||||
|  | ||||
|  | ||||
|     private val downloaderConnection = object : ServiceConnection { | ||||
|         override fun onServiceConnected(name: ComponentName?, service: IBinder?) { | ||||
|             val binder = service as DownloaderService.DownloaderBinder | ||||
|             downloaderService = binder.getService() | ||||
|             isDownloaderServiceBound = true | ||||
|         } | ||||
|  | ||||
|         override fun onServiceDisconnected(name: ComponentName?) { | ||||
|             isDownloaderServiceBound = false | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private val mediaPlayerConnection = object : ServiceConnection { | ||||
|         override fun onServiceConnected(name: ComponentName?, service: IBinder?) { | ||||
|             val binder = service as MediaPlayerService.MediaPlayerBinder | ||||
|             mediaPlayerService = binder.getService() | ||||
|             isMediaPlayerServiceBound = true | ||||
|         } | ||||
|  | ||||
|         override fun onServiceDisconnected(name: ComponentName?) { | ||||
|             isMediaPlayerServiceBound = false | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun onStart() { | ||||
|         super.onStart() | ||||
|         ContextCompat.startForegroundService(this, Intent(this, MediaPlayerService::class.java)) | ||||
|         Intent(this, MediaPlayerService::class.java).also { | ||||
|             bindService(it, mediaPlayerConnection, Context.BIND_AUTO_CREATE) | ||||
|         } | ||||
|         Intent(this, DownloaderService::class.java).also { | ||||
|             bindService(it, downloaderConnection, Context.BIND_AUTO_CREATE) | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     override fun onStop() { | ||||
|         super.onStop() | ||||
|         unbindService(mediaPlayerConnection) | ||||
|         unbindService(downloaderConnection) | ||||
|         isMediaPlayerServiceBound = false | ||||
|         isDownloaderServiceBound = false | ||||
|     } | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|  | ||||
|         // Upgrade Youtube-dl | ||||
| //        YT.updateYoutubeDL(application) | ||||
|  | ||||
|         // Set fullscreen immersive mode | ||||
|         setContentView(R.layout.activity_fullscreen) | ||||
|         fullscreen_content.systemUiVisibility = | ||||
|             View.SYSTEM_UI_FLAG_LOW_PROFILE or | ||||
|                     View.SYSTEM_UI_FLAG_FULLSCREEN or | ||||
|                     View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or | ||||
|                     View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or | ||||
|                     View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or | ||||
|                     View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | ||||
|  | ||||
|  | ||||
|         fullscreen_content_controls.visibility = View.VISIBLE | ||||
|     } | ||||
|  | ||||
|     @RequiresApi(Build.VERSION_CODES.O) | ||||
|     fun startPlaylist(v: View){ | ||||
|         playButton.setOnClickListener{} // Remove listner | ||||
|         playButton.isEnabled = false | ||||
|  | ||||
|         if (!isMediaPlayerServiceBound || !isDownloaderServiceBound){ | ||||
|             println("Problem connecting to services") | ||||
|             println("Media player status: $isMediaPlayerServiceBound") | ||||
|             println("Downloader status: $isDownloaderServiceBound") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         // Setup title update lambda | ||||
|         mediaPlayerService.titleUpdater = { | ||||
|             runOnUiThread(Runnable { | ||||
|                 currently_playing.text = " $it    ".repeat(400) | ||||
|                 currently_playing.isSelected = true | ||||
|             }) | ||||
|         } | ||||
|         // Setup image update lambda | ||||
|         mediaPlayerService.thumbnailUpdater = { | ||||
|             runOnUiThread(Runnable { | ||||
|                 thumbnail_view.setImageURI(Uri.parse(it)) | ||||
|             }) | ||||
|         } | ||||
|  | ||||
|  | ||||
|         downloaderService.startDownloading(mediaPlayerService, downloadBar) | ||||
|         mediaPlayerService.startPlaying() | ||||
|     } | ||||
| } | ||||
							
								
								
									
										275
									
								
								app/src/main/java/zoo/wbooster/Services.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								app/src/main/java/zoo/wbooster/Services.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,275 @@ | ||||
| package zoo.wbooster | ||||
|  | ||||
| import android.app.* | ||||
| import android.content.Intent | ||||
| import android.media.MediaPlayer | ||||
| import android.os.Binder | ||||
| import android.os.Build | ||||
| import android.os.IBinder | ||||
| import android.util.Log | ||||
| import android.widget.ProgressBar | ||||
| import androidx.annotation.RequiresApi | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.core.app.NotificationCompat | ||||
| import com.beust.klaxon.Klaxon | ||||
| import com.vdurmont.emoji.EmojiParser | ||||
| import com.yausername.ffmpeg.FFmpeg | ||||
| import com.yausername.youtubedl_android.YoutubeDL | ||||
| import com.yausername.youtubedl_android.YoutubeDLException | ||||
| import com.yausername.youtubedl_android.YoutubeDLRequest | ||||
| import java.lang.Exception | ||||
| import java.util.* | ||||
| import java.util.concurrent.ConcurrentLinkedQueue | ||||
|  | ||||
| class VideoUsefulDetails( | ||||
|     val path: String, | ||||
|     val title: String, | ||||
|     val thumbnail: String | ||||
| ) | ||||
|  | ||||
| class VideoInfo( | ||||
|     val _type: String, | ||||
|     val id: String, | ||||
|     val ie_key: String, | ||||
|     val title: String, | ||||
|     val url: String | ||||
| ) | ||||
|  | ||||
| class PlaylistInfos( | ||||
|     val _type: String, | ||||
|     val extractor: String, | ||||
|     val extractor_key: String, | ||||
|     val id: String, | ||||
|     val title: String, | ||||
|     val uploader: String, | ||||
|     val uploader_id: String, | ||||
|     val uploader_url: String, | ||||
|     val webpage_url: String, | ||||
|     val webpage_url_basename: String, | ||||
|     val entries: Array<VideoInfo> | ||||
| ){ | ||||
|     fun getRandomVideo() = this.entries.get(Random().nextInt(this.entries.size)) | ||||
| } | ||||
|  | ||||
| class DownloaderService: Service() { | ||||
|  | ||||
|     private var playlist: PlaylistInfos? = null | ||||
|     private val binder = DownloaderBinder() | ||||
|     private var isStarted = false | ||||
|  | ||||
|     inner class DownloaderBinder: Binder() { | ||||
|         fun getService(): DownloaderService = this@DownloaderService | ||||
|     } | ||||
|  | ||||
|     private fun customInit(){ | ||||
|         if (isStarted) return | ||||
|  | ||||
|         // Initialize Youtube-dl | ||||
|         // Thanks to https://github.com/yausername/youtubedl-android | ||||
|         try { | ||||
|             YT.init(application) | ||||
|         } catch (e: YoutubeDLException) { | ||||
|             Log.e("failed to initialize youtubedl-android", e.localizedMessage) | ||||
|         } | ||||
|         try { | ||||
|             FFmpeg.getInstance().init(application) | ||||
|         } catch (e: Exception) { | ||||
|             Log.e("failed to initialize FFmpeg", e.localizedMessage) | ||||
|         } | ||||
|  | ||||
|         playlist = downloadPlaylistInfo() | ||||
|         isStarted = true | ||||
|     } | ||||
|  | ||||
|     private fun downloadPlaylistInfo(): PlaylistInfos?{ | ||||
|         val request = YoutubeDLRequest(PLAYLIST_URL) | ||||
|         request.setOption("--dump-single-json") | ||||
|         request.setOption("--flat-playlist") | ||||
|         try { | ||||
|             return Klaxon().parse<PlaylistInfos>(YT.execute(request).out) | ||||
|         } catch (e: Exception) { | ||||
|             println("Error downloading playlist infos") | ||||
|             println(e) | ||||
|             return null | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun startDownloading(mediaPlayerService: MediaPlayerService, downloadBar: ProgressBar) { | ||||
|  | ||||
|         if (playlist == null){ | ||||
|             println("No fucking playlist info") | ||||
|             return | ||||
|         } | ||||
|  | ||||
|         Thread(Runnable { | ||||
|             while (true) { | ||||
|                 val video = playlist?.getRandomVideo() | ||||
|                 val url = video?.url ?: "" | ||||
|                 val info = try { | ||||
|                     YT.getInfo(url) | ||||
|                 } catch(e: YoutubeDLException) { | ||||
|                     println("Error getting infos for video, skipping") | ||||
|                     println(e) | ||||
|                     continue | ||||
|                 } | ||||
|  | ||||
|                 val title = EmojiParser.removeAllEmojis(info.title) | ||||
|  | ||||
|                 if (title == null){ | ||||
|                     println("Error with music title, skipping") | ||||
|                     continue | ||||
|                 } | ||||
|  | ||||
|                 val pathTemplate = "${applicationContext.filesDir}/${title}--${video?.id}" | ||||
|                 val filePath = "$pathTemplate.mp3" | ||||
|                 val thumnailPath = "$pathTemplate.jpg" | ||||
|  | ||||
|                 val request = YoutubeDLRequest(url) | ||||
|                 request.setOption("-x") | ||||
|                 request.setOption("--audio-format", "mp3") | ||||
|                 request.setOption("--write-thumbnail") | ||||
|                 request.setOption("--cache-dir", "${applicationContext.filesDir}/.cache") | ||||
|                 request.setOption("-o", "$pathTemplate.%(ext)s") | ||||
|  | ||||
|  | ||||
|  | ||||
|                 try { | ||||
|                     YT.execute(request) { progress, etaInSeconds -> | ||||
|                         println("$progress% (ETA $etaInSeconds seconds)") | ||||
|                         downloadBar.progress = progress.toInt() | ||||
|                     } | ||||
|                     mediaPlayerService.addNextSong(VideoUsefulDetails(filePath, info.title, thumnailPath)) | ||||
|                 } catch (e: Exception) { | ||||
|                     println(e) | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         }).start() | ||||
|     } | ||||
|  | ||||
|     override fun onBind(intent: Intent?): IBinder? { | ||||
|         customInit() | ||||
|         return binder | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         /** | ||||
|          * URL to the WBooster playlist | ||||
|          */ | ||||
|         private val PLAYLIST_URL = | ||||
|             "https://www.youtube.com/playlist?list=PLQC73oq6JtgyYNOeifrJXXzZ1-F0Kgmbg" | ||||
|         private val YT = YoutubeDL.getInstance() | ||||
|     } | ||||
| } | ||||
|  | ||||
| class MediaPlayerService: Service(), MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener { | ||||
|  | ||||
|     private lateinit var notificationManager: NotificationManager | ||||
|     private lateinit var notificationBuilder: NotificationCompat.Builder | ||||
|     private var playerThread: Thread? = null | ||||
|     private val binder = MediaPlayerBinder() | ||||
|     private var mMediaPlayer: MediaPlayer? = null | ||||
|     private var playlistQueue: Queue<VideoUsefulDetails> = ConcurrentLinkedQueue<VideoUsefulDetails>() | ||||
|     private var currentSong: VideoUsefulDetails? = null | ||||
|     var titleUpdater = {title: String -> } | ||||
|     var thumbnailUpdater = { path: String ->} | ||||
|  | ||||
|  | ||||
|     inner class MediaPlayerBinder: Binder() { | ||||
|         fun getService(): MediaPlayerService = this@MediaPlayerService | ||||
|     } | ||||
|  | ||||
|     fun addNextSong(details: VideoUsefulDetails) { | ||||
|         this.playlistQueue.add(details) | ||||
|     } | ||||
|  | ||||
|     /* Get next music path and update interface */ | ||||
|     private fun updateNextSong(){ | ||||
|  | ||||
|         /* poll next song */ | ||||
|         currentSong = playlistQueue.poll() | ||||
|         while (currentSong == null){ | ||||
|             Thread.sleep(1000) /* Wait for another element */ | ||||
|             currentSong = playlistQueue.poll() | ||||
|         } | ||||
|  | ||||
|         // Update notification | ||||
|         notificationBuilder.setContentText(currentSong!!.title) | ||||
|         val notification = notificationBuilder.build() | ||||
|         notification.flags = Notification.FLAG_ONGOING_EVENT | ||||
|         notificationManager.notify(1, notificationBuilder.build()) | ||||
|  | ||||
|         // Update title | ||||
|         titleUpdater(currentSong!!.title) | ||||
|  | ||||
|         // Update image | ||||
|         thumbnailUpdater(currentSong!!.thumbnail) | ||||
|  | ||||
|     } | ||||
|  | ||||
|     fun startPlaying() { | ||||
|         playerThread?.interrupt() | ||||
|         playerThread = Thread(Runnable { | ||||
|             updateNextSong() | ||||
|             /* launch media player */ | ||||
|             mMediaPlayer = MediaPlayer().apply { | ||||
|                 setDataSource(currentSong!!.path) | ||||
|                 setOnPreparedListener(this@MediaPlayerService) | ||||
|                 setOnCompletionListener(this@MediaPlayerService) | ||||
|                 prepareAsync() // prepare async to not block main thread | ||||
|             } | ||||
|         }) | ||||
|         /* Start media player thread */ | ||||
|         playerThread?.start() | ||||
|     } | ||||
|  | ||||
|     @RequiresApi(Build.VERSION_CODES.O) | ||||
|     override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { | ||||
|  | ||||
|         // Create Notification channel | ||||
|         val serviceChannel = NotificationChannel( | ||||
|             CHANNEL_ID, | ||||
|             "Foreground music channel", | ||||
|             NotificationManager.IMPORTANCE_DEFAULT | ||||
|         ) | ||||
|         notificationManager = getSystemService(NotificationManager::class.java) | ||||
|         notificationManager.createNotificationChannel(serviceChannel) | ||||
|  | ||||
|         val notificationIntent = Intent(this, AppCompatActivity::class.java) | ||||
|         val pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0) | ||||
|         notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID) | ||||
|             .setContentTitle(getText(R.string.app_name)) | ||||
|             .setContentText("") | ||||
|             .setSmallIcon(R.drawable.ic_launcher_foreground) | ||||
|             .setContentIntent(pendingIntent) | ||||
|  | ||||
|         startForeground(1, notificationBuilder.build()) | ||||
|  | ||||
|         return START_NOT_STICKY | ||||
|     } | ||||
|  | ||||
|     override fun onBind(intent: Intent?) = binder | ||||
|  | ||||
|     /** Called when MediaPlayer is ready */ | ||||
|     override fun onPrepared(mediaPlayer: MediaPlayer) { | ||||
|         mediaPlayer.start() | ||||
|     } | ||||
|  | ||||
|     override fun onCompletion(mp: MediaPlayer?) { | ||||
|         updateNextSong() | ||||
|         mp?.reset() | ||||
|         mp?.setDataSource(currentSong!!.path) | ||||
|         mp?.prepareAsync() | ||||
|     } | ||||
|  | ||||
|     override fun onDestroy() { | ||||
|         super.onDestroy() | ||||
|         mMediaPlayer?.release() | ||||
|         playerThread?.interrupt() | ||||
|         stopSelf() | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         val CHANNEL_ID = "ForegroundMediaPlayer" | ||||
|     } | ||||
| } | ||||
							
								
								
									
										170
									
								
								app/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								app/src/main/res/drawable/ic_launcher_background.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,170 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportWidth="108" | ||||
|     android:viewportHeight="108"> | ||||
|     <path | ||||
|         android:fillColor="#008577" | ||||
|         android:pathData="M0,0h108v108h-108z" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M9,0L9,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,0L19,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,0L29,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,0L39,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,0L49,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,0L59,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,0L69,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,0L79,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M89,0L89,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M99,0L99,108" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,9L108,9" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,19L108,19" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,29L108,29" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,39L108,39" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,49L108,49" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,59L108,59" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,69L108,69" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,79L108,79" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,89L108,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M0,99L108,99" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,29L89,29" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,39L89,39" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,49L89,49" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,59L89,59" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,69L89,69" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M19,79L89,79" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M29,19L29,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M39,19L39,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M49,19L49,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M59,19L59,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M69,19L69,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
|     <path | ||||
|         android:fillColor="#00000000" | ||||
|         android:pathData="M79,19L79,89" | ||||
|         android:strokeWidth="0.8" | ||||
|         android:strokeColor="#33FFFFFF" /> | ||||
| </vector> | ||||
							
								
								
									
										34
									
								
								app/src/main/res/drawable/ic_launcher_foreground.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/src/main/res/drawable/ic_launcher_foreground.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:aapt="http://schemas.android.com/aapt" | ||||
|     android:width="108dp" | ||||
|     android:height="108dp" | ||||
|     android:viewportWidth="108" | ||||
|     android:viewportHeight="108"> | ||||
|     <path | ||||
|         android:fillType="evenOdd" | ||||
|         android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" | ||||
|         android:strokeWidth="1" | ||||
|         android:strokeColor="#00000000"> | ||||
|         <aapt:attr name="android:fillColor"> | ||||
|             <gradient | ||||
|                 android:endX="78.5885" | ||||
|                 android:endY="90.9159" | ||||
|                 android:startX="48.7653" | ||||
|                 android:startY="61.0927" | ||||
|                 android:type="linear"> | ||||
|                 <item | ||||
|                     android:color="#44000000" | ||||
|                     android:offset="0.0" /> | ||||
|                 <item | ||||
|                     android:color="#00000000" | ||||
|                     android:offset="1.0" /> | ||||
|             </gradient> | ||||
|         </aapt:attr> | ||||
|     </path> | ||||
|     <path | ||||
|         android:fillColor="#FFFFFF" | ||||
|         android:fillType="nonZero" | ||||
|         android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" | ||||
|         android:strokeWidth="1" | ||||
|         android:strokeColor="#00000000" /> | ||||
| </vector> | ||||
							
								
								
									
										74
									
								
								app/src/main/res/layout/activity_fullscreen.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								app/src/main/res/layout/activity_fullscreen.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="fill_parent" | ||||
|     android:background="#ff0099cc"> | ||||
|  | ||||
|     <TextView | ||||
|         android:id="@+id/fullscreen_content" | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="fill_parent" | ||||
|         android:gravity="center" | ||||
|         android:keepScreenOn="true" | ||||
|         android:textColor="#ff33b5e5" | ||||
|         android:textSize="50.0sp" | ||||
|         android:textStyle="bold" /> | ||||
|  | ||||
|     <FrameLayout | ||||
|         android:layout_width="fill_parent" | ||||
|         android:layout_height="fill_parent" | ||||
|         android:fitsSystemWindows="true"> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="fill_parent" | ||||
|             android:orientation="vertical"> | ||||
|  | ||||
|             <ProgressBar | ||||
|                 android:id="@+id/downloadBar" | ||||
|                 style="?android:progressBarStyleHorizontal" | ||||
|                 android:layout_width="fill_parent" | ||||
|                 android:layout_height="28.0dip" /> | ||||
|  | ||||
|             <TextView | ||||
|                 android:id="@+id/currently_playing" | ||||
|                 android:layout_width="fill_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:ellipsize="marquee" | ||||
|                 android:focusable="true" | ||||
|                 android:marqueeRepeatLimit="marquee_forever" | ||||
|                 android:scrollHorizontally="true" | ||||
|                 android:singleLine="true" | ||||
|                 android:textColor="@android:color/white" /> | ||||
|  | ||||
|             <ImageView | ||||
|                 android:id="@+id/thumbnail_view" | ||||
|                 tools:ignore="ContentDescription" | ||||
|                 android:layout_width="fill_parent" | ||||
|                 android:layout_height="496.0dip" | ||||
|                 tools:srcCompat="@tools:sample/avatars" /> | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <LinearLayout | ||||
|             android:id="@+id/fullscreen_content_controls" | ||||
|             style="?metaButtonBarStyle" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_gravity="bottom|center" | ||||
|             android:background="@color/black_overlay" | ||||
|             android:orientation="horizontal"> | ||||
|  | ||||
|             <Button | ||||
|                 android:id="@+id/playButton" | ||||
|                 style="?metaButtonBarButtonStyle" | ||||
|                 android:layout_width="0.0dip" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_gravity="bottom" | ||||
|                 android:layout_weight="1.0" | ||||
|                 android:onClick="startPlaylist" | ||||
|                 android:text="@string/play_button" /> | ||||
|         </LinearLayout> | ||||
|     </FrameLayout> | ||||
| </FrameLayout> | ||||
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-hdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 12 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-mdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xhdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 20 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxhdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 42 KiB | 
							
								
								
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
									
									
									
									
									
										Executable file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 70 KiB | 
							
								
								
									
										5
									
								
								app/src/main/res/values-fr/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/values-fr/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="app_name">WBoosterAndroid</string> | ||||
|     <string name="play_button">Jouer</string> | ||||
| </resources> | ||||
							
								
								
									
										12
									
								
								app/src/main/res/values/attrs.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/src/main/res/values/attrs.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| <resources> | ||||
|  | ||||
|     <!-- Declare custom theme attributes that allow changing which styles are | ||||
|          used for button bars depending on the API level. | ||||
|          ?android:attr/buttonBarStyle is new as of API 11 so this is | ||||
|          necessary to support previous API levels. --> | ||||
|     <declare-styleable name="ButtonBarContainerTheme"> | ||||
|         <attr name="metaButtonBarStyle" format="reference" /> | ||||
|         <attr name="metaButtonBarButtonStyle" format="reference" /> | ||||
|     </declare-styleable> | ||||
|  | ||||
| </resources> | ||||
							
								
								
									
										8
									
								
								app/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/src/main/res/values/colors.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <color name="colorPrimary">#008577</color> | ||||
|     <color name="colorPrimaryDark">#00574B</color> | ||||
|     <color name="colorAccent">#D81B60</color> | ||||
|  | ||||
|     <color name="black_overlay">#66000000</color> | ||||
| </resources> | ||||
							
								
								
									
										5
									
								
								app/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/src/main/res/values/strings.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <string name="app_name">WBoosterAndroid</string> | ||||
|     <string name="play_button">Play</string> | ||||
| </resources> | ||||
							
								
								
									
										23
									
								
								app/src/main/res/values/styles.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/src/main/res/values/styles.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| <resources> | ||||
|  | ||||
|     <!-- Base application theme. --> | ||||
|     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> | ||||
|         <!-- Customize your theme here. --> | ||||
|         <item name="colorPrimary">@color/colorPrimary</item> | ||||
|         <item name="colorPrimaryDark">@color/colorPrimaryDark</item> | ||||
|         <item name="colorAccent">@color/colorAccent</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="FullscreenTheme" parent="AppTheme"> | ||||
|         <item name="android:actionBarStyle">@style/FullscreenActionBarStyle</item> | ||||
|         <item name="android:windowActionBarOverlay">true</item> | ||||
|         <item name="android:windowBackground">@null</item> | ||||
|         <item name="metaButtonBarStyle">?android:attr/buttonBarStyle</item> | ||||
|         <item name="metaButtonBarButtonStyle">?android:attr/buttonBarButtonStyle</item> | ||||
|     </style> | ||||
|  | ||||
|     <style name="FullscreenActionBarStyle" parent="Widget.AppCompat.ActionBar"> | ||||
|         <item name="android:background">@color/black_overlay</item> | ||||
|     </style> | ||||
|  | ||||
| </resources> | ||||
							
								
								
									
										17
									
								
								app/src/test/java/zoo/wbooster/ExampleUnitTest.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/src/test/java/zoo/wbooster/ExampleUnitTest.kt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| package zoo.wbooster | ||||
|  | ||||
| import org.junit.Test | ||||
|  | ||||
| import org.junit.Assert.* | ||||
|  | ||||
| /** | ||||
|  * Example local unit test, which will execute on the development machine (host). | ||||
|  * | ||||
|  * See [testing documentation](http://d.android.com/tools/testing). | ||||
|  */ | ||||
| class ExampleUnitTest { | ||||
|     @Test | ||||
|     fun addition_isCorrect() { | ||||
|         assertEquals(4, 2 + 2) | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user