android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: exoplayer
|
|
3
|
+
description: >
|
|
4
|
+
ExoPlayer (Media3) setup and usage for advanced media playback on Android.
|
|
5
|
+
Load this skill when implementing audio/video streaming, HLS/DASH playback,
|
|
6
|
+
background audio with MediaSession, playlist management, or custom renderers.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ExoPlayer (Media3)
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
ExoPlayer, now part of AndroidX Media3, is the recommended media player for Android. It supports HTTP streaming (HLS, DASH, SmoothStreaming), local files, playlists, gapless audio, and deep MediaSession integration. It replaces `MediaPlayer` for all but the simplest use cases.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- Use **Media3 ExoPlayer** — not the standalone ExoPlayer library (deprecated)
|
|
19
|
+
- Create one `ExoPlayer` instance per playback session — not per screen
|
|
20
|
+
- Use **MediaSession** for background audio and system/notification controls
|
|
21
|
+
- Release the player when done — `player.release()` frees all resources
|
|
22
|
+
- For background audio, use a `MediaSessionService` (foreground service)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
[versions]
|
|
30
|
+
media3 = "1.4.0"
|
|
31
|
+
|
|
32
|
+
[libraries]
|
|
33
|
+
media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3" }
|
|
34
|
+
media3-exoplayer-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "media3" }
|
|
35
|
+
media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3" }
|
|
36
|
+
media3-session = { module = "androidx.media3:media3-session", version.ref = "media3" }
|
|
37
|
+
media3-datasource-okhttp = { module = "androidx.media3:media3-datasource-okhttp", version.ref = "media3" }
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```kotlin
|
|
41
|
+
dependencies {
|
|
42
|
+
implementation(libs.media3.exoplayer)
|
|
43
|
+
implementation(libs.media3.exoplayer.hls)
|
|
44
|
+
implementation(libs.media3.ui)
|
|
45
|
+
implementation(libs.media3.session)
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Basic Playback
|
|
52
|
+
|
|
53
|
+
```kotlin
|
|
54
|
+
// ✅ Create and configure player
|
|
55
|
+
val player = ExoPlayer.Builder(context)
|
|
56
|
+
.setHandleAudioBecomingNoisy(true) // pause on headphone unplug
|
|
57
|
+
.setAudioAttributes(
|
|
58
|
+
AudioAttributes.Builder()
|
|
59
|
+
.setContentType(C.AUDIO_CONTENT_TYPE_MUSIC)
|
|
60
|
+
.setUsage(C.USAGE_MEDIA)
|
|
61
|
+
.build(),
|
|
62
|
+
true // handle audio focus automatically
|
|
63
|
+
)
|
|
64
|
+
.build()
|
|
65
|
+
|
|
66
|
+
// ✅ Set media item and play
|
|
67
|
+
val mediaItem = MediaItem.fromUri("https://example.com/audio.mp3")
|
|
68
|
+
player.setMediaItem(mediaItem)
|
|
69
|
+
player.prepare()
|
|
70
|
+
player.play()
|
|
71
|
+
|
|
72
|
+
// ✅ Local file
|
|
73
|
+
val mediaItem = MediaItem.fromUri(Uri.fromFile(File(context.filesDir, "audio.mp3")))
|
|
74
|
+
|
|
75
|
+
// ✅ HLS stream
|
|
76
|
+
val mediaItem = MediaItem.Builder()
|
|
77
|
+
.setUri("https://example.com/stream.m3u8")
|
|
78
|
+
.setMimeType(MimeTypes.APPLICATION_M3U8)
|
|
79
|
+
.build()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Playlist Management
|
|
85
|
+
|
|
86
|
+
```kotlin
|
|
87
|
+
// ✅ Multiple media items
|
|
88
|
+
val playlist = listOf(
|
|
89
|
+
MediaItem.fromUri("https://example.com/track1.mp3"),
|
|
90
|
+
MediaItem.fromUri("https://example.com/track2.mp3"),
|
|
91
|
+
MediaItem.fromUri("https://example.com/track3.mp3")
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
player.setMediaItems(playlist)
|
|
95
|
+
player.prepare()
|
|
96
|
+
player.play()
|
|
97
|
+
|
|
98
|
+
// ✅ Navigate playlist
|
|
99
|
+
player.seekToNextMediaItem()
|
|
100
|
+
player.seekToPreviousMediaItem()
|
|
101
|
+
player.seekToMediaItem(index = 2)
|
|
102
|
+
|
|
103
|
+
// ✅ Repeat and shuffle
|
|
104
|
+
player.repeatMode = Player.REPEAT_MODE_ALL // repeat playlist
|
|
105
|
+
player.shuffleModeEnabled = true
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Player Controls in Compose
|
|
111
|
+
|
|
112
|
+
```kotlin
|
|
113
|
+
// ✅ PlayerView in Compose
|
|
114
|
+
@Composable
|
|
115
|
+
fun VideoPlayer(videoUri: Uri) {
|
|
116
|
+
val context = LocalContext.current
|
|
117
|
+
|
|
118
|
+
val player = remember {
|
|
119
|
+
ExoPlayer.Builder(context).build().apply {
|
|
120
|
+
setMediaItem(MediaItem.fromUri(videoUri))
|
|
121
|
+
prepare()
|
|
122
|
+
playWhenReady = true
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
DisposableEffect(Unit) {
|
|
127
|
+
onDispose { player.release() }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
AndroidView(
|
|
131
|
+
factory = {
|
|
132
|
+
PlayerView(it).apply {
|
|
133
|
+
this.player = player
|
|
134
|
+
useController = true
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
modifier = Modifier.fillMaxWidth().aspectRatio(16f / 9f)
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Player Listeners
|
|
145
|
+
|
|
146
|
+
```kotlin
|
|
147
|
+
// ✅ Listen to playback state changes
|
|
148
|
+
player.addListener(object : Player.Listener {
|
|
149
|
+
|
|
150
|
+
override fun onPlaybackStateChanged(playbackState: Int) {
|
|
151
|
+
when (playbackState) {
|
|
152
|
+
Player.STATE_IDLE -> { /* not started */ }
|
|
153
|
+
Player.STATE_BUFFERING -> showBufferingIndicator()
|
|
154
|
+
Player.STATE_READY -> hideBufferingIndicator()
|
|
155
|
+
Player.STATE_ENDED -> onPlaybackEnded()
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
|
160
|
+
updatePlayPauseButton(isPlaying)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
override fun onPlayerError(error: PlaybackException) {
|
|
164
|
+
Log.e("ExoPlayer", "Playback error: ${error.message}")
|
|
165
|
+
showError(error.message)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
|
169
|
+
updateNowPlayingUI(mediaItem)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Background Audio with MediaSession
|
|
177
|
+
|
|
178
|
+
```kotlin
|
|
179
|
+
// ✅ MediaSessionService for background audio
|
|
180
|
+
@AndroidEntryPoint
|
|
181
|
+
class PlaybackService : MediaSessionService() {
|
|
182
|
+
|
|
183
|
+
private lateinit var player: ExoPlayer
|
|
184
|
+
private lateinit var mediaSession: MediaSession
|
|
185
|
+
|
|
186
|
+
override fun onCreate() {
|
|
187
|
+
super.onCreate()
|
|
188
|
+
player = ExoPlayer.Builder(this)
|
|
189
|
+
.setHandleAudioBecomingNoisy(true)
|
|
190
|
+
.build()
|
|
191
|
+
|
|
192
|
+
mediaSession = MediaSession.Builder(this, player)
|
|
193
|
+
.setCallback(MediaSessionCallback())
|
|
194
|
+
.build()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession =
|
|
198
|
+
mediaSession
|
|
199
|
+
|
|
200
|
+
override fun onDestroy() {
|
|
201
|
+
mediaSession.release()
|
|
202
|
+
player.release()
|
|
203
|
+
super.onDestroy()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private inner class MediaSessionCallback : MediaSession.Callback {
|
|
207
|
+
override fun onAddMediaItems(
|
|
208
|
+
mediaSession: MediaSession,
|
|
209
|
+
controller: MediaSession.ControllerInfo,
|
|
210
|
+
mediaItems: List<MediaItem>
|
|
211
|
+
): ListenableFuture<List<MediaItem>> = Futures.immediateFuture(mediaItems)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// AndroidManifest.xml
|
|
216
|
+
// <service android:name=".PlaybackService"
|
|
217
|
+
// android:exported="true"
|
|
218
|
+
// android:foregroundServiceType="mediaPlayback">
|
|
219
|
+
// <intent-filter>
|
|
220
|
+
// <action android:name="androidx.media3.session.MediaSessionService"/>
|
|
221
|
+
// </intent-filter>
|
|
222
|
+
// </service>
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Custom OkHttp Data Source
|
|
228
|
+
|
|
229
|
+
```kotlin
|
|
230
|
+
// ✅ Use OkHttp for network requests (auth headers, custom interceptors)
|
|
231
|
+
val dataSourceFactory = OkHttpDataSource.Factory(okHttpClient)
|
|
232
|
+
.setDefaultRequestProperties(mapOf("Authorization" to "Bearer $token"))
|
|
233
|
+
|
|
234
|
+
val player = ExoPlayer.Builder(context)
|
|
235
|
+
.setMediaSourceFactory(
|
|
236
|
+
DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
|
|
237
|
+
)
|
|
238
|
+
.build()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Anti-Patterns
|
|
244
|
+
|
|
245
|
+
- Creating a new `ExoPlayer` instance per screen — expensive, loses state on navigation
|
|
246
|
+
- Not calling `player.release()` — holds audio/video hardware
|
|
247
|
+
- Not setting `setHandleAudioBecomingNoisy(true)` — audio leaks to speaker on unplug
|
|
248
|
+
- Background audio without `MediaSessionService` — system kills playback
|
|
249
|
+
- Using legacy ExoPlayer library instead of Media3 — deprecated
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Related Skills
|
|
254
|
+
- `audio` — basic audio playback with MediaPlayer
|
|
255
|
+
- `video` — basic video playback
|
|
256
|
+
- `foreground-service` — required for background audio
|
|
257
|
+
- `notification` — media notification controls
|
|
258
|
+
- `lifecycle` — releasing player with lifecycle
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: video
|
|
3
|
+
description: >
|
|
4
|
+
Android video playback and capture patterns.
|
|
5
|
+
Load this skill when playing local or streaming video, capturing video,
|
|
6
|
+
or handling video output on Android. For advanced playback use ExoPlayer.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Video
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
Android provides `VideoView` and `MediaPlayer` for simple local video playback, and `MediaRecorder` for video capture. For streaming, adaptive bitrate, or advanced playback, use ExoPlayer. This skill covers the foundational video APIs.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- Use **ExoPlayer** for streaming video, adaptive bitrate (HLS/DASH), or any non-trivial playback
|
|
19
|
+
- Use `VideoView` only for simple local file playback with minimal customization
|
|
20
|
+
- Video capture requires **both** `CAMERA` and `RECORD_AUDIO` permissions
|
|
21
|
+
- Always **release** `MediaPlayer`/`MediaRecorder` — they hold exclusive hardware
|
|
22
|
+
- Handle **lifecycle** — pause video when app goes to background
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## VideoView — Simple Playback
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
// ✅ Play local video in XML layout
|
|
30
|
+
// layout/fragment_video.xml
|
|
31
|
+
// <VideoView android:id="@+id/videoView" ... />
|
|
32
|
+
|
|
33
|
+
// In Fragment
|
|
34
|
+
val videoView = binding.videoView
|
|
35
|
+
val mediaController = MediaController(requireContext())
|
|
36
|
+
mediaController.setAnchorView(videoView)
|
|
37
|
+
|
|
38
|
+
videoView.apply {
|
|
39
|
+
setMediaController(mediaController)
|
|
40
|
+
setVideoURI(Uri.parse("android.resource://${context.packageName}/${R.raw.sample_video}"))
|
|
41
|
+
setOnPreparedListener { start() }
|
|
42
|
+
setOnCompletionListener { /* handle completion */ }
|
|
43
|
+
setOnErrorListener { _, what, extra ->
|
|
44
|
+
Log.e("VideoView", "Error: what=$what extra=$extra")
|
|
45
|
+
true
|
|
46
|
+
}
|
|
47
|
+
requestFocus()
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## MediaPlayer — Programmatic Video Playback
|
|
54
|
+
|
|
55
|
+
```kotlin
|
|
56
|
+
// ✅ VideoView alternative with more control
|
|
57
|
+
class VideoPlayer(private val context: Context) {
|
|
58
|
+
|
|
59
|
+
private var mediaPlayer: MediaPlayer? = null
|
|
60
|
+
|
|
61
|
+
fun playOnSurface(surface: Surface, videoUri: Uri) {
|
|
62
|
+
release()
|
|
63
|
+
mediaPlayer = MediaPlayer().apply {
|
|
64
|
+
setAudioAttributes(
|
|
65
|
+
AudioAttributes.Builder()
|
|
66
|
+
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
|
|
67
|
+
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
68
|
+
.build()
|
|
69
|
+
)
|
|
70
|
+
setDataSource(context, videoUri)
|
|
71
|
+
setSurface(surface)
|
|
72
|
+
setOnPreparedListener { start() }
|
|
73
|
+
prepareAsync()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fun pause() { mediaPlayer?.pause() }
|
|
78
|
+
fun resume() { mediaPlayer?.start() }
|
|
79
|
+
|
|
80
|
+
fun release() {
|
|
81
|
+
mediaPlayer?.release()
|
|
82
|
+
mediaPlayer = null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
val currentPosition: Int get() = mediaPlayer?.currentPosition ?: 0
|
|
86
|
+
val duration: Int get() = mediaPlayer?.duration ?: 0
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Video Capture with CameraX
|
|
93
|
+
|
|
94
|
+
```kotlin
|
|
95
|
+
// ✅ Preferred — use CameraX VideoCapture use case
|
|
96
|
+
// See camerax skill for full setup
|
|
97
|
+
|
|
98
|
+
val recorder = Recorder.Builder()
|
|
99
|
+
.setQualitySelector(
|
|
100
|
+
QualitySelector.fromOrderedList(
|
|
101
|
+
listOf(Quality.FHD, Quality.HD, Quality.SD),
|
|
102
|
+
FallbackStrategy.lowerQualityOrHigherThan(Quality.SD)
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
.build()
|
|
106
|
+
|
|
107
|
+
val videoCapture = VideoCapture.withOutput(recorder)
|
|
108
|
+
|
|
109
|
+
// Start recording
|
|
110
|
+
var recording: Recording? = null
|
|
111
|
+
|
|
112
|
+
fun startRecording(context: Context, outputFile: File) {
|
|
113
|
+
recording = videoCapture.output
|
|
114
|
+
.prepareRecording(context, FileOutputOptions.Builder(outputFile).build())
|
|
115
|
+
.withAudioEnabled()
|
|
116
|
+
.start(ContextCompat.getMainExecutor(context)) { event ->
|
|
117
|
+
when (event) {
|
|
118
|
+
is VideoRecordEvent.Start -> onRecordingStarted()
|
|
119
|
+
is VideoRecordEvent.Finalize -> {
|
|
120
|
+
if (!event.hasError()) onRecordingSaved(event.outputResults.outputUri)
|
|
121
|
+
else onRecordingError(event.error)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fun stopRecording() {
|
|
128
|
+
recording?.stop()
|
|
129
|
+
recording = null
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Video Capture with MediaRecorder (Legacy)
|
|
136
|
+
|
|
137
|
+
```kotlin
|
|
138
|
+
// ✅ Use when CameraX is not available or Camera2 integration is needed
|
|
139
|
+
class LegacyVideoRecorder(private val context: Context) {
|
|
140
|
+
|
|
141
|
+
private var mediaRecorder: MediaRecorder? = null
|
|
142
|
+
|
|
143
|
+
fun startRecording(surface: Surface, outputFile: File) {
|
|
144
|
+
mediaRecorder = MediaRecorder(context).apply {
|
|
145
|
+
setAudioSource(MediaRecorder.AudioSource.MIC)
|
|
146
|
+
setVideoSource(MediaRecorder.VideoSource.SURFACE)
|
|
147
|
+
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
|
148
|
+
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
|
149
|
+
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
|
|
150
|
+
setVideoSize(1920, 1080)
|
|
151
|
+
setVideoFrameRate(30)
|
|
152
|
+
setVideoEncodingBitRate(10_000_000)
|
|
153
|
+
setOutputFile(outputFile.absolutePath)
|
|
154
|
+
prepare()
|
|
155
|
+
}
|
|
156
|
+
mediaRecorder?.start()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fun stopRecording() {
|
|
160
|
+
mediaRecorder?.apply {
|
|
161
|
+
stop()
|
|
162
|
+
release()
|
|
163
|
+
}
|
|
164
|
+
mediaRecorder = null
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Thumbnail Generation
|
|
172
|
+
|
|
173
|
+
```kotlin
|
|
174
|
+
// ✅ Generate video thumbnail
|
|
175
|
+
suspend fun getVideoThumbnail(context: Context, videoUri: Uri): Bitmap? {
|
|
176
|
+
return withContext(Dispatchers.IO) {
|
|
177
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
178
|
+
context.contentResolver.loadThumbnail(videoUri, Size(640, 360), null)
|
|
179
|
+
} else {
|
|
180
|
+
MediaMetadataRetriever().use { retriever ->
|
|
181
|
+
retriever.setDataSource(context, videoUri)
|
|
182
|
+
retriever.getFrameAtTime(0)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Lifecycle Handling
|
|
192
|
+
|
|
193
|
+
```kotlin
|
|
194
|
+
// ✅ In Fragment — pause/resume with lifecycle
|
|
195
|
+
override fun onPause() {
|
|
196
|
+
super.onPause()
|
|
197
|
+
videoPlayer.pause()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
override fun onResume() {
|
|
201
|
+
super.onResume()
|
|
202
|
+
videoPlayer.resume()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
override fun onDestroyView() {
|
|
206
|
+
videoPlayer.release()
|
|
207
|
+
super.onDestroyView()
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Anti-Patterns
|
|
214
|
+
|
|
215
|
+
- Using `VideoView` for streaming — no buffering control, poor UX; use ExoPlayer
|
|
216
|
+
- Not releasing `MediaPlayer`/`MediaRecorder` — hardware lock
|
|
217
|
+
- Video playback in background without a `ForegroundService` — system kills it
|
|
218
|
+
- Capturing video without checking both `CAMERA` and `RECORD_AUDIO` permissions
|
|
219
|
+
- Blocking main thread with `prepare()` — always use `prepareAsync()`
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Related Skills
|
|
224
|
+
- `exoplayer` — advanced video/audio playback
|
|
225
|
+
- `camerax` — video capture via CameraX
|
|
226
|
+
- `audio` — audio-only recording and playback
|
|
227
|
+
- `foreground-service` — background video playback
|
|
228
|
+
- `lifecycle` — releasing video resources on lifecycle events
|