T
# Projeto exemplo: VideoWallpaperApp (Kotlin)
Este documento contém os arquivos principais para um app Android que: 1) permite ao usuário escolher um vídeo do dispositivo; 2) guarda a URI com permissão persistente; 3) fornece um WallpaperService que reproduz esse vídeo como papel de parede animado (sem áudio e em loop).
---
## 1) README (instruções rápidas)
1. Importar esse projeto no Android Studio (Kotlin, minSdkVersion 21+ recomendado).
2. Rodar no dispositivo (emulador pode não suportar live wallpaper).
3. No app, toque em "Escolher vídeo" e selecione um vídeo pela interface do sistema (Ação `ACTION_OPEN_DOCUMENT`).
4. Depois de escolher, toque em "Definir como live wallpaper" — o Android abrirá a tela de escolha de live wallpaper já apontando para o serviço.
Observações: use `takePersistableUriPermission(...)` (feito no exemplo) para manter acesso ao arquivo. O serviço usa `MediaPlayer` e a `SurfaceHolder` para desenhar o vídeo.
---
## 2) app/src/main/java/com/example/videowallpaper/MainActivity.kt
```kotlin
package com.example.videowallpaper
import android.app.WallpaperManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.example.videowallpaper.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val prefsName = "video_wallpaper_prefs"
private val keyUri = "video_uri"
private val pickVideo = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
uri?.let {
// Persistir permissão
contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
getSharedPreferences(prefsName, Context.MODE_PRIVATE)
.edit()
.putString(keyUri, it.toString())
.apply()
binding.tvStatus.text = "Vídeo salvo: ${it.lastPathSegment}"
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnChoose.setOnClickListener {
// Tip: documentos persistentes -> use "video/*"
pickVideo.launch(arrayOf("video/*"))
}
binding.btnSetWallpaper.setOnClickListener {
// Abrir a tela para trocar o live wallpaper apontando para o serviço
val intent = Intent(WallpaperManager.ACTION_CHANGE_LIVE_WALLPAPER)
intent.putExtra(
WallpaperManager.EXTRA_LIVE_WALLPAPER_COMPONENT,
ComponentName(this, VideoWallpaperService::class.java)
)
startActivity(intent)
}
// Mostra status
val saved = getSharedPreferences(prefsName, Context.MODE_PRIVATE).getString(keyUri, null)
binding.tvStatus.text = if (saved != null) "Vídeo selecionado" else "Nenhum vídeo selecionado"
}
}
```
---
## 3) app/src/main/java/com/example/videowallpaper/VideoWallpaperService.kt
```kotlin
package com.example.videowallpaper
import android.content.Context
import android.content.SharedPreferences
import android.media.MediaPlayer
import android.net.Uri
import android.service.wallpaper.WallpaperService
import android.view.SurfaceHolder
class VideoWallpaperService : WallpaperService() {
override fun onCreateEngine(): Engine {
return VideoEngine()
}
inner class VideoEngine : Engine(), SurfaceHolder.Callback, MediaPlayer.OnPreparedListener {
private var mediaPlayer: MediaPlayer? = null
private val prefsName = "video_wallpaper_prefs"
private val keyUri = "video_uri"
override fun onSurfaceCreated(holder: SurfaceHolder) {
super.onSurfaceCreated(holder)
holder.addCallback(this)
}
override fun onSurfaceDestroyed(holder: SurfaceHolder) {
super.onSurfaceDestroyed(holder)
releasePlayer()
}
override fun surfaceCreated(holder: SurfaceHolder) {
// Não iniciar aqui — aguardar onVisibilityChanged(true)
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
// sem-op
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
releasePlayer()
}
override fun onVisibilityChanged(visible: Boolean) {
if (visible) startOrResume() else pausePlayer()
}
private fun startOrResume() {
val prefs = applicationContext.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
val uriString = prefs.getString(keyUri, null) ?: return
val uri = Uri.parse(uriString)
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer()
try {
mediaPlayer?.apply {
setSurface(surfaceHolder.surface)
setDataSource(applicationContext, uri)
isLooping = true
setVolume(0f, 0f) // sem áudio
setOnPreparedListener(this@VideoEngine)
prepareAsync()
}
} catch (e: Exception) {
e.printStackTrace()
releasePlayer()
}
} else {
mediaPlayer?.start()
}
}
private fun pausePlayer() {
mediaPlayer?.pause()
}
override fun onPrepared(mp: MediaPlayer) {
mp.start()
}
private fun releasePlayer() {
try {
mediaPlayer?.stop()
} catch (_: Exception) {}
mediaPlayer?.release()
mediaPlayer = null
}
private val surfaceHolder: SurfaceHolder
get() = this@VideoEngine.surfaceHolder
}
}
```
---
## 4) app/src/main/AndroidManifest.xml
```xml
```
---
## 5) app/src/main/res/xml/wallpaper.xml
```xml
```
---
## 6) app/src/main/res/layout/activity_main.xml
```xml
```
---
## 7) app/build.gradle (module)
```gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 34
defaultConfig {
applicationId "com.example.videowallpaper"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.9.0"
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.core:core-ktx:1.10.0'
}
```
---
## Notas finais / Melhorias possíveis
- Tratar casos de rotação/tamanhos e ajustar `MediaPlayer` para dimensionar o vídeo corretamente (usar `SurfaceTexture` e `Matrix` para escalonar).
- Adicionar controle de performance (pausar quando tela bloqueada) — já parcialmente coberto por `onVisibilityChanged`.
- Permissões: em Android 11+ prefira `Storage Access Framework` (este exemplo usa `ACTION_OPEN_DOCUMENT`).
- Para suportar GIFs, webm etc, testar compatibilidade do `MediaPlayer` ou usar ExoPlayer.
---
FIM
Comentários
Postar um comentário