Add some previews.
This commit is contained in:
parent
81679cceae
commit
556fc38f62
5 changed files with 367 additions and 40 deletions
|
|
@ -29,16 +29,20 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio
|
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.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.mediaviewer.api.MediaInfo
|
import io.element.android.libraries.mediaviewer.api.MediaInfo
|
||||||
import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize
|
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.LocalMediaViewState
|
||||||
|
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MediaFileView(
|
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 = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<MediaInfo> {
|
||||||
|
override val values: Sequence<MediaInfo>
|
||||||
|
get() = sequenceOf(
|
||||||
|
aPdfMediaInfo(),
|
||||||
|
anAudioMediaInfo(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -14,9 +14,12 @@ import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
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.designsystem.utils.CommonDrawables
|
||||||
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
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.LocalMediaViewState
|
||||||
|
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage
|
import me.saket.telephoto.zoomable.coil.ZoomableAsyncImage
|
||||||
import me.saket.telephoto.zoomable.rememberZoomableImageState
|
import me.saket.telephoto.zoomable.rememberZoomableImageState
|
||||||
|
|
@ -47,3 +50,13 @@ fun MediaImageView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun MediaImageViewPreview() = ElementPreview {
|
||||||
|
MediaImageView(
|
||||||
|
localMediaViewState = rememberLocalMediaViewState(),
|
||||||
|
localMedia = null,
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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<MediaItem>) {}
|
||||||
|
override fun setMediaItems(mediaItems: MutableList<MediaItem>, resetPosition: Boolean) {}
|
||||||
|
override fun setMediaItems(mediaItems: MutableList<MediaItem>, 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<MediaItem>) {}
|
||||||
|
override fun addMediaItems(index: Int, mediaItems: MutableList<MediaItem>) {}
|
||||||
|
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<MediaItem>) {}
|
||||||
|
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<MediaSource>) {}
|
||||||
|
override fun setMediaSources(mediaSources: MutableList<MediaSource>, resetPosition: Boolean) {}
|
||||||
|
override fun setMediaSources(mediaSources: MutableList<MediaSource>, 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<MediaSource>) {}
|
||||||
|
override fun addMediaSources(index: Int, mediaSources: MutableList<MediaSource>) {}
|
||||||
|
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<Effect>) {}
|
||||||
|
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?) {}
|
||||||
|
}
|
||||||
|
|
@ -32,51 +32,60 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.Timeline
|
import androidx.media3.common.Timeline
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
import io.element.android.compound.theme.ElementTheme
|
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.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.utils.KeepScreenOn
|
import io.element.android.libraries.designsystem.utils.KeepScreenOn
|
||||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||||
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
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.LocalMediaViewState
|
||||||
import io.element.android.libraries.mediaviewer.impl.local.PlayableState
|
import io.element.android.libraries.mediaviewer.impl.local.PlayableState
|
||||||
|
import io.element.android.libraries.mediaviewer.impl.local.rememberLocalMediaViewState
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
@Composable
|
@Composable
|
||||||
fun MediaVideoView(
|
fun MediaVideoView(
|
||||||
localMediaViewState: LocalMediaViewState,
|
localMediaViewState: LocalMediaViewState,
|
||||||
localMedia: LocalMedia?,
|
localMedia: LocalMedia?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
if (LocalInspectionMode.current) {
|
val exoPlayer = if (LocalInspectionMode.current) {
|
||||||
Text(
|
remember {
|
||||||
modifier = modifier
|
ExoPlayerForPreview()
|
||||||
.background(ElementTheme.colors.bgSubtlePrimary)
|
}
|
||||||
.wrapContentSize(),
|
|
||||||
text = "A Video Player will render here",
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
ExoPlayerMediaVideoView(
|
val context = LocalContext.current
|
||||||
localMediaViewState = localMediaViewState,
|
remember {
|
||||||
localMedia = localMedia,
|
ExoPlayerWrapper.create(context)
|
||||||
modifier = modifier,
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
ExoPlayerMediaVideoView(
|
||||||
|
localMediaViewState = localMediaViewState,
|
||||||
|
exoPlayer = exoPlayer,
|
||||||
|
localMedia = localMedia,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExoPlayerMediaVideoView(
|
private fun ExoPlayerMediaVideoView(
|
||||||
localMediaViewState: LocalMediaViewState,
|
localMediaViewState: LocalMediaViewState,
|
||||||
|
exoPlayer: ExoPlayer,
|
||||||
localMedia: LocalMedia?,
|
localMedia: LocalMedia?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
val isControllerVisibleByDefault = LocalInspectionMode.current
|
||||||
var mediaPlayerControllerState: MediaPlayerControllerState by remember {
|
var mediaPlayerControllerState: MediaPlayerControllerState by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
MediaPlayerControllerState(
|
MediaPlayerControllerState(
|
||||||
isVisible = false,
|
isVisible = isControllerVisibleByDefault,
|
||||||
isPlaying = false,
|
isPlaying = false,
|
||||||
progressInMillis = 0,
|
progressInMillis = 0,
|
||||||
durationInMillis = 0,
|
durationInMillis = 0,
|
||||||
|
|
@ -95,10 +104,6 @@ private fun ExoPlayerMediaVideoView(
|
||||||
|
|
||||||
localMediaViewState.playableState = playableState
|
localMediaViewState.playableState = playableState
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val exoPlayer = remember {
|
|
||||||
ExoPlayerWrapper.create(context)
|
|
||||||
}
|
|
||||||
val playerListener = object : Player.Listener {
|
val playerListener = object : Player.Listener {
|
||||||
override fun onRenderedFirstFrame() {
|
override fun onRenderedFirstFrame() {
|
||||||
localMediaViewState.isReady = true
|
localMediaViewState.isReady = true
|
||||||
|
|
@ -167,31 +172,40 @@ private fun ExoPlayerMediaVideoView(
|
||||||
KeepScreenOn(mediaPlayerControllerState.isPlaying)
|
KeepScreenOn(mediaPlayerControllerState.isPlaying)
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.background(ElementTheme.colors.bgSubtlePrimary)
|
.background(ElementTheme.colors.bgSubtlePrimary),
|
||||||
.wrapContentSize(),
|
|
||||||
) {
|
) {
|
||||||
AndroidView(
|
val context = LocalContext.current
|
||||||
modifier = Modifier.fillMaxSize(),
|
if (LocalInspectionMode.current) {
|
||||||
factory = {
|
Text(
|
||||||
PlayerView(context).apply {
|
modifier = Modifier
|
||||||
player = exoPlayer
|
.background(ElementTheme.colors.bgSubtlePrimary)
|
||||||
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT
|
.wrapContentSize(),
|
||||||
layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
text = "A Video Player will render here",
|
||||||
setOnClickListener {
|
)
|
||||||
autoHideController++
|
} else {
|
||||||
mediaPlayerControllerState = mediaPlayerControllerState.copy(
|
AndroidView(
|
||||||
isVisible = !mediaPlayerControllerState.isVisible,
|
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)
|
||||||
onRelease = { playerView ->
|
playerView.setControllerVisibilityListener(null as PlayerView.ControllerVisibilityListener?)
|
||||||
playerView.setOnClickListener(null)
|
playerView.player = null
|
||||||
playerView.setControllerVisibilityListener(null as PlayerView.ControllerVisibilityListener?)
|
},
|
||||||
playerView.player = null
|
)
|
||||||
},
|
}
|
||||||
)
|
|
||||||
MediaPlayerControllerView(
|
MediaPlayerControllerView(
|
||||||
state = mediaPlayerControllerState,
|
state = mediaPlayerControllerState,
|
||||||
onTogglePlay = {
|
onTogglePlay = {
|
||||||
|
|
@ -235,3 +249,13 @@ private fun ExoPlayerMediaVideoView(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun MediaVideoViewPreview() = ElementPreview {
|
||||||
|
MediaVideoView(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
localMediaViewState = rememberLocalMediaViewState(),
|
||||||
|
localMedia = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue