diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt index f0663a5d23..0e345138a0 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaFileView.kt @@ -29,16 +29,20 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.rotate import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.mediaviewer.api.MediaInfo import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState +import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState @Composable fun MediaFileView( @@ -101,3 +105,16 @@ fun MediaFileView( } } } + +@PreviewsDayNight +@Composable +internal fun MediaFileViewPreview( + @PreviewParameter(MediaInfoFileProvider::class) info: MediaInfo +) = ElementPreview { + MediaFileView( + localMediaViewState = rememberLocalMediaViewState(), + uri = null, + info = info, + onClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt new file mode 100644 index 0000000000..980f9eba89 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/file/MediaInfoFileProvider.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.mediaviewer.impl.local.file + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.mediaviewer.api.MediaInfo +import io.element.android.libraries.mediaviewer.api.aPdfMediaInfo +import io.element.android.libraries.mediaviewer.api.anAudioMediaInfo + +open class MediaInfoFileProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + aPdfMediaInfo(), + anAudioMediaInfo(), + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt index c5792a9828..7057bd8b74 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/image/MediaImageView.kt @@ -14,9 +14,12 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalInspectionMode import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState +import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import io.element.android.libraries.ui.strings.CommonStrings import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage import me.saket.telephoto.zoomable.rememberZoomableImageState @@ -47,3 +50,13 @@ fun MediaImageView( ) } } + +@PreviewsDayNight +@Composable +internal fun MediaImageViewPreview() = ElementPreview { + MediaImageView( + localMediaViewState = rememberLocalMediaViewState(), + localMedia = null, + onClick = {}, + ) +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerForPreview.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerForPreview.kt new file mode 100644 index 0000000000..21f107ec95 --- /dev/null +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/ExoPlayerForPreview.kt @@ -0,0 +1,252 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +@file:Suppress( + "OVERRIDE_DEPRECATION", + "RedundantNullableReturnType", + "DEPRECATION", +) + +package io.element.android.libraries.mediaviewer.impl.local.video + +import android.annotation.SuppressLint +import android.media.AudioDeviceInfo +import android.os.Looper +import android.view.Surface +import android.view.SurfaceHolder +import android.view.SurfaceView +import android.view.TextureView +import androidx.media3.common.AudioAttributes +import androidx.media3.common.AuxEffectInfo +import androidx.media3.common.DeviceInfo +import androidx.media3.common.Effect +import androidx.media3.common.Format +import androidx.media3.common.MediaItem +import androidx.media3.common.MediaMetadata +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.Player +import androidx.media3.common.PriorityTaskManager +import androidx.media3.common.Timeline +import androidx.media3.common.TrackSelectionParameters +import androidx.media3.common.Tracks +import androidx.media3.common.VideoSize +import androidx.media3.common.text.CueGroup +import androidx.media3.common.util.Clock +import androidx.media3.common.util.Size +import androidx.media3.exoplayer.DecoderCounters +import androidx.media3.exoplayer.ExoPlaybackException +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.PlayerMessage +import androidx.media3.exoplayer.Renderer +import androidx.media3.exoplayer.SeekParameters +import androidx.media3.exoplayer.analytics.AnalyticsCollector +import androidx.media3.exoplayer.analytics.AnalyticsListener +import androidx.media3.exoplayer.image.ImageOutput +import androidx.media3.exoplayer.source.MediaSource +import androidx.media3.exoplayer.source.ShuffleOrder +import androidx.media3.exoplayer.source.TrackGroupArray +import androidx.media3.exoplayer.trackselection.TrackSelectionArray +import androidx.media3.exoplayer.trackselection.TrackSelector +import androidx.media3.exoplayer.video.VideoFrameMetadataListener +import androidx.media3.exoplayer.video.spherical.CameraMotionListener + +@SuppressLint("UnsafeOptInUsageError") +class ExoPlayerForPreview( + private val isPlaying: Boolean = false, +) : ExoPlayer { + override fun getApplicationLooper(): Looper = throw NotImplementedError() + override fun addListener(listener: Player.Listener) {} + override fun removeListener(listener: Player.Listener) {} + override fun setMediaItems(mediaItems: MutableList) {} + override fun setMediaItems(mediaItems: MutableList, resetPosition: Boolean) {} + override fun setMediaItems(mediaItems: MutableList, startIndex: Int, startPositionMs: Long) {} + override fun setMediaItem(mediaItem: MediaItem) {} + override fun setMediaItem(mediaItem: MediaItem, startPositionMs: Long) {} + override fun setMediaItem(mediaItem: MediaItem, resetPosition: Boolean) {} + override fun addMediaItem(mediaItem: MediaItem) {} + override fun addMediaItem(index: Int, mediaItem: MediaItem) {} + override fun addMediaItems(mediaItems: MutableList) {} + override fun addMediaItems(index: Int, mediaItems: MutableList) {} + override fun moveMediaItem(currentIndex: Int, newIndex: Int) {} + override fun moveMediaItems(fromIndex: Int, toIndex: Int, newIndex: Int) {} + override fun replaceMediaItem(index: Int, mediaItem: MediaItem) {} + override fun replaceMediaItems(fromIndex: Int, toIndex: Int, mediaItems: MutableList) {} + override fun removeMediaItem(index: Int) {} + override fun removeMediaItems(fromIndex: Int, toIndex: Int) {} + override fun clearMediaItems() {} + override fun isCommandAvailable(command: Int): Boolean = throw NotImplementedError() + override fun canAdvertiseSession(): Boolean = throw NotImplementedError() + override fun getAvailableCommands(): Player.Commands = throw NotImplementedError() + override fun prepare(mediaSource: MediaSource) {} + override fun prepare(mediaSource: MediaSource, resetPosition: Boolean, resetState: Boolean) {} + override fun prepare() {} + override fun getPlaybackState(): Int = throw NotImplementedError() + override fun getPlaybackSuppressionReason(): Int = throw NotImplementedError() + override fun isPlaying() = isPlaying + override fun getPlayerError(): ExoPlaybackException? = null + override fun play() {} + override fun pause() {} + override fun setPlayWhenReady(playWhenReady: Boolean) {} + override fun getPlayWhenReady(): Boolean = throw NotImplementedError() + override fun setRepeatMode(repeatMode: Int) {} + override fun getRepeatMode(): Int = throw NotImplementedError() + override fun setShuffleModeEnabled(shuffleModeEnabled: Boolean) {} + override fun getShuffleModeEnabled(): Boolean = throw NotImplementedError() + override fun isLoading(): Boolean = throw NotImplementedError() + override fun seekToDefaultPosition() {} + override fun seekToDefaultPosition(mediaItemIndex: Int) {} + override fun seekTo(positionMs: Long) {} + override fun seekTo(mediaItemIndex: Int, positionMs: Long) {} + override fun getSeekBackIncrement(): Long = throw NotImplementedError() + override fun seekBack() {} + override fun getSeekForwardIncrement(): Long = throw NotImplementedError() + override fun seekForward() {} + override fun hasPreviousMediaItem(): Boolean = throw NotImplementedError() + override fun seekToPreviousWindow() {} + override fun seekToPreviousMediaItem() {} + override fun getMaxSeekToPreviousPosition(): Long = throw NotImplementedError() + override fun seekToPrevious() {} + override fun hasNext(): Boolean = throw NotImplementedError() + override fun hasNextWindow(): Boolean = throw NotImplementedError() + override fun hasNextMediaItem(): Boolean = throw NotImplementedError() + override fun next() {} + override fun seekToNextWindow() {} + override fun seekToNextMediaItem() {} + override fun seekToNext() {} + override fun setPlaybackParameters(playbackParameters: PlaybackParameters) {} + override fun setPlaybackSpeed(speed: Float) {} + override fun getPlaybackParameters(): PlaybackParameters = throw NotImplementedError() + override fun stop() {} + override fun release() {} + override fun getCurrentTracks(): Tracks = throw NotImplementedError() + override fun getTrackSelectionParameters(): TrackSelectionParameters = throw NotImplementedError() + override fun setTrackSelectionParameters(parameters: TrackSelectionParameters) {} + override fun getMediaMetadata(): MediaMetadata = throw NotImplementedError() + override fun getPlaylistMetadata(): MediaMetadata = throw NotImplementedError() + override fun setPlaylistMetadata(mediaMetadata: MediaMetadata) {} + override fun getCurrentManifest(): Any? = throw NotImplementedError() + override fun getCurrentTimeline(): Timeline = throw NotImplementedError() + override fun getCurrentPeriodIndex(): Int = throw NotImplementedError() + override fun getCurrentWindowIndex(): Int = throw NotImplementedError() + override fun getCurrentMediaItemIndex(): Int = throw NotImplementedError() + override fun getNextWindowIndex(): Int = throw NotImplementedError() + override fun getNextMediaItemIndex(): Int = throw NotImplementedError() + override fun getPreviousWindowIndex(): Int = throw NotImplementedError() + override fun getPreviousMediaItemIndex(): Int = throw NotImplementedError() + override fun getCurrentMediaItem(): MediaItem? = throw NotImplementedError() + override fun getMediaItemCount(): Int = throw NotImplementedError() + override fun getMediaItemAt(index: Int): MediaItem = throw NotImplementedError() + override fun getDuration(): Long = throw NotImplementedError() + override fun getCurrentPosition(): Long = throw NotImplementedError() + override fun getBufferedPosition(): Long = throw NotImplementedError() + override fun getBufferedPercentage(): Int = throw NotImplementedError() + override fun getTotalBufferedDuration(): Long = throw NotImplementedError() + override fun isCurrentWindowDynamic(): Boolean = throw NotImplementedError() + override fun isCurrentMediaItemDynamic(): Boolean = throw NotImplementedError() + override fun isCurrentWindowLive(): Boolean = throw NotImplementedError() + override fun isCurrentMediaItemLive(): Boolean = throw NotImplementedError() + override fun getCurrentLiveOffset(): Long = throw NotImplementedError() + override fun isCurrentWindowSeekable(): Boolean = throw NotImplementedError() + override fun isCurrentMediaItemSeekable(): Boolean = throw NotImplementedError() + override fun isPlayingAd(): Boolean = throw NotImplementedError() + override fun getCurrentAdGroupIndex(): Int = throw NotImplementedError() + override fun getCurrentAdIndexInAdGroup(): Int = throw NotImplementedError() + override fun getContentDuration(): Long = throw NotImplementedError() + override fun getContentPosition(): Long = throw NotImplementedError() + override fun getContentBufferedPosition(): Long = throw NotImplementedError() + override fun getAudioAttributes(): AudioAttributes = throw NotImplementedError() + override fun setVolume(volume: Float) = throw NotImplementedError() + override fun getVolume(): Float = throw NotImplementedError() + override fun clearVideoSurface() {} + override fun clearVideoSurface(surface: Surface?) {} + override fun setVideoSurface(surface: Surface?) {} + override fun setVideoSurfaceHolder(surfaceHolder: SurfaceHolder?) {} + override fun clearVideoSurfaceHolder(surfaceHolder: SurfaceHolder?) {} + override fun setVideoSurfaceView(surfaceView: SurfaceView?) {} + override fun clearVideoSurfaceView(surfaceView: SurfaceView?) {} + override fun setVideoTextureView(textureView: TextureView?) {} + override fun clearVideoTextureView(textureView: TextureView?) {} + override fun getVideoSize(): VideoSize = throw NotImplementedError() + override fun getSurfaceSize(): Size = throw NotImplementedError() + override fun getCurrentCues(): CueGroup = throw NotImplementedError() + override fun getDeviceInfo(): DeviceInfo = throw NotImplementedError() + override fun getDeviceVolume(): Int = throw NotImplementedError() + override fun isDeviceMuted(): Boolean = throw NotImplementedError() + override fun setDeviceVolume(volume: Int) {} + override fun setDeviceVolume(volume: Int, flags: Int) {} + override fun increaseDeviceVolume() {} + override fun increaseDeviceVolume(flags: Int) {} + override fun decreaseDeviceVolume() {} + override fun decreaseDeviceVolume(flags: Int) {} + override fun setDeviceMuted(muted: Boolean) {} + override fun setDeviceMuted(muted: Boolean, flags: Int) {} + override fun setAudioAttributes(audioAttributes: AudioAttributes, handleAudioFocus: Boolean) {} + override fun getAudioComponent(): ExoPlayer.AudioComponent? = throw NotImplementedError() + override fun getVideoComponent(): ExoPlayer.VideoComponent? = throw NotImplementedError() + override fun getTextComponent(): ExoPlayer.TextComponent? = throw NotImplementedError() + override fun getDeviceComponent(): ExoPlayer.DeviceComponent? = throw NotImplementedError() + override fun addAudioOffloadListener(listener: ExoPlayer.AudioOffloadListener) {} + override fun removeAudioOffloadListener(listener: ExoPlayer.AudioOffloadListener) {} + override fun getAnalyticsCollector(): AnalyticsCollector = throw NotImplementedError() + override fun addAnalyticsListener(listener: AnalyticsListener) {} + override fun removeAnalyticsListener(listener: AnalyticsListener) {} + override fun getRendererCount(): Int = throw NotImplementedError() + override fun getRendererType(index: Int): Int = throw NotImplementedError() + override fun getRenderer(index: Int): Renderer = throw NotImplementedError() + override fun getTrackSelector(): TrackSelector? = throw NotImplementedError() + override fun getCurrentTrackGroups(): TrackGroupArray = throw NotImplementedError() + override fun getCurrentTrackSelections(): TrackSelectionArray = throw NotImplementedError() + override fun getPlaybackLooper(): Looper = throw NotImplementedError() + override fun getClock(): Clock = throw NotImplementedError() + override fun setMediaSources(mediaSources: MutableList) {} + override fun setMediaSources(mediaSources: MutableList, resetPosition: Boolean) {} + override fun setMediaSources(mediaSources: MutableList, startMediaItemIndex: Int, startPositionMs: Long) {} + override fun setMediaSource(mediaSource: MediaSource) {} + override fun setMediaSource(mediaSource: MediaSource, startPositionMs: Long) {} + override fun setMediaSource(mediaSource: MediaSource, resetPosition: Boolean) {} + override fun addMediaSource(mediaSource: MediaSource) {} + override fun addMediaSource(index: Int, mediaSource: MediaSource) {} + override fun addMediaSources(mediaSources: MutableList) {} + override fun addMediaSources(index: Int, mediaSources: MutableList) {} + override fun setShuffleOrder(shuffleOrder: ShuffleOrder) {} + override fun setPreloadConfiguration(preloadConfiguration: ExoPlayer.PreloadConfiguration) {} + override fun getPreloadConfiguration(): ExoPlayer.PreloadConfiguration = throw NotImplementedError() + override fun setAudioSessionId(audioSessionId: Int) {} + override fun getAudioSessionId(): Int = throw NotImplementedError() + override fun setAuxEffectInfo(auxEffectInfo: AuxEffectInfo) {} + override fun clearAuxEffectInfo() {} + override fun setPreferredAudioDevice(audioDeviceInfo: AudioDeviceInfo?) {} + override fun setSkipSilenceEnabled(skipSilenceEnabled: Boolean) {} + override fun getSkipSilenceEnabled(): Boolean = throw NotImplementedError() + override fun setVideoEffects(videoEffects: MutableList) {} + override fun setVideoScalingMode(videoScalingMode: Int) {} + override fun getVideoScalingMode(): Int = throw NotImplementedError() + override fun setVideoChangeFrameRateStrategy(videoChangeFrameRateStrategy: Int) {} + override fun getVideoChangeFrameRateStrategy(): Int = throw NotImplementedError() + override fun setVideoFrameMetadataListener(listener: VideoFrameMetadataListener) {} + override fun clearVideoFrameMetadataListener(listener: VideoFrameMetadataListener) {} + override fun setCameraMotionListener(listener: CameraMotionListener) {} + override fun clearCameraMotionListener(listener: CameraMotionListener) {} + override fun createMessage(target: PlayerMessage.Target): PlayerMessage = throw NotImplementedError() + override fun setSeekParameters(seekParameters: SeekParameters?) {} + override fun getSeekParameters(): SeekParameters = throw NotImplementedError() + override fun setForegroundMode(foregroundMode: Boolean) {} + override fun setPauseAtEndOfMediaItems(pauseAtEndOfMediaItems: Boolean) {} + override fun getPauseAtEndOfMediaItems(): Boolean = throw NotImplementedError() + override fun getAudioFormat(): Format? = throw NotImplementedError() + override fun getVideoFormat(): Format? = throw NotImplementedError() + override fun getAudioDecoderCounters(): DecoderCounters? = throw NotImplementedError() + override fun getVideoDecoderCounters(): DecoderCounters? = throw NotImplementedError() + override fun setHandleAudioBecomingNoisy(handleAudioBecomingNoisy: Boolean) {} + override fun setWakeMode(wakeMode: Int) {} + override fun setPriority(priority: Int) {} + override fun setPriorityTaskManager(priorityTaskManager: PriorityTaskManager?) {} + override fun isSleepingForOffload(): Boolean = throw NotImplementedError() + override fun isTunnelingEnabled(): Boolean = throw NotImplementedError() + override fun isReleased(): Boolean = throw NotImplementedError() + override fun setImageOutput(imageOutput: ImageOutput?) {} +} diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt index 7c7d798e93..2aef883752 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/local/video/MediaVideoView.kt @@ -32,51 +32,60 @@ import androidx.lifecycle.Lifecycle import androidx.media3.common.MediaItem import androidx.media3.common.Player import androidx.media3.common.Timeline +import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.KeepScreenOn import io.element.android.libraries.designsystem.utils.OnLifecycleEvent import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.impl.local.LocalMediaViewState import io.element.android.libraries.mediaviewer.impl.local.PlayableState +import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState import kotlinx.coroutines.delay import kotlin.time.Duration.Companion.seconds +@SuppressLint("UnsafeOptInUsageError") @Composable fun MediaVideoView( localMediaViewState: LocalMediaViewState, localMedia: LocalMedia?, modifier: Modifier = Modifier, ) { - if (LocalInspectionMode.current) { - Text( - modifier = modifier - .background(ElementTheme.colors.bgSubtlePrimary) - .wrapContentSize(), - text = "A Video Player will render here", - ) + val exoPlayer = if (LocalInspectionMode.current) { + remember { + ExoPlayerForPreview() + } } else { - ExoPlayerMediaVideoView( - localMediaViewState = localMediaViewState, - localMedia = localMedia, - modifier = modifier, - ) + val context = LocalContext.current + remember { + ExoPlayerWrapper.create(context) + } } + ExoPlayerMediaVideoView( + localMediaViewState = localMediaViewState, + exoPlayer = exoPlayer, + localMedia = localMedia, + modifier = modifier, + ) } @SuppressLint("UnsafeOptInUsageError") @Composable private fun ExoPlayerMediaVideoView( localMediaViewState: LocalMediaViewState, + exoPlayer: ExoPlayer, localMedia: LocalMedia?, modifier: Modifier = Modifier, ) { + val isControllerVisibleByDefault = LocalInspectionMode.current var mediaPlayerControllerState: MediaPlayerControllerState by remember { mutableStateOf( MediaPlayerControllerState( - isVisible = false, + isVisible = isControllerVisibleByDefault, isPlaying = false, progressInMillis = 0, durationInMillis = 0, @@ -95,10 +104,6 @@ private fun ExoPlayerMediaVideoView( localMediaViewState.playableState = playableState - val context = LocalContext.current - val exoPlayer = remember { - ExoPlayerWrapper.create(context) - } val playerListener = object : Player.Listener { override fun onRenderedFirstFrame() { localMediaViewState.isReady = true @@ -167,31 +172,40 @@ private fun ExoPlayerMediaVideoView( KeepScreenOn(mediaPlayerControllerState.isPlaying) Box( modifier = modifier - .background(ElementTheme.colors.bgSubtlePrimary) - .wrapContentSize(), + .background(ElementTheme.colors.bgSubtlePrimary), ) { - AndroidView( - modifier = Modifier.fillMaxSize(), - factory = { - PlayerView(context).apply { - player = exoPlayer - resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT - layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) - setOnClickListener { - autoHideController++ - mediaPlayerControllerState = mediaPlayerControllerState.copy( - isVisible = !mediaPlayerControllerState.isVisible, - ) + val context = LocalContext.current + if (LocalInspectionMode.current) { + Text( + modifier = Modifier + .background(ElementTheme.colors.bgSubtlePrimary) + .wrapContentSize(), + text = "A Video Player will render here", + ) + } else { + AndroidView( + modifier = Modifier.fillMaxSize(), + factory = { + PlayerView(context).apply { + player = exoPlayer + resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT + layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) + setOnClickListener { + autoHideController++ + mediaPlayerControllerState = mediaPlayerControllerState.copy( + isVisible = !mediaPlayerControllerState.isVisible, + ) + } + useController = false } - useController = false - } - }, - onRelease = { playerView -> - playerView.setOnClickListener(null) - playerView.setControllerVisibilityListener(null as PlayerView.ControllerVisibilityListener?) - playerView.player = null - }, - ) + }, + onRelease = { playerView -> + playerView.setOnClickListener(null) + playerView.setControllerVisibilityListener(null as PlayerView.ControllerVisibilityListener?) + playerView.player = null + }, + ) + } MediaPlayerControllerView( state = mediaPlayerControllerState, onTogglePlay = { @@ -235,3 +249,13 @@ private fun ExoPlayerMediaVideoView( } } } + +@PreviewsDayNight +@Composable +internal fun MediaVideoViewPreview() = ElementPreview { + MediaVideoView( + modifier = Modifier.fillMaxSize(), + localMediaViewState = rememberLocalMediaViewState(), + localMedia = null, + ) +}