Initial commit
This commit is contained in:
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>
|
Reference in New Issue
Block a user