We can do this by using LoaderManager with callback, however it seems deprecated both support or non-support version from android-O

So, Let’s do it by using ContentResolver

illustration

FOR LOADING ALL VIDEOS FROM DEVICE

Create a contract

This help you separate the content that you need, and easily to

class LocalVideoContract {
    companion object {
        const val VIDEO_NAME = MediaStore.Video.Media.DISPLAY_NAME
        const val VIDEO_PATH = MediaStore.Video.Media.DATA
        const val VIDEO_TYPE = MediaStore.Video.Media.MIME_TYPE
        const val VIDEO_DURATION = MediaStore.Video.Media.DURATION
        const val VIDEO_SIZE = MediaStore.Video.Media.SIZE
        const val VIDEO_DATE_ADDED = MediaStore.Video.Media.DATE_ADDED
        const val VIDEO_ALBUM = MediaStore.Video.Media.ALBUM
        const val VIDEO_RESOLUTION = MediaStore.Video.Media.RESOLUTION

        // The Uri path
        fun queryUri(): Uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI

        fun projection() = arrayOf(VIDEO_NAME, VIDEO_PATH, VIDEO_TYPE, VIDEO_DURATION, VIDEO_SIZE, VIDEO_DATE_ADDED, VIDEO_ALBUM, VIDEO_RESOLUTION)

        fun selection(): String {
            val argsSize = selectionArgs().size
            val str = StringBuilder()
            for (i in 0 until argsSize) {
                if (i > 0) {
                    str.append(",")
                }
                str.append("?")
            }
            // For support load multi types of video
            return VIDEO_TYPE.plus(" in (").plus(str.toString()).plus(")")
        }

        fun selectionArgs() = arrayOf("video/mp4", "video/m4v", "video/mkv", "video/mov", "video/avi", "video/ts", "video/webm")

        // Order by Last modified
        fun queryOrder() = "$VIDEO_DATE_ADDED DESC"
    }
}

Load the videos

// Note that LocalVideoData is your defined model of local video
fun fetchVideos(): Single<List<LocalVideoData>> {
    return Single.create<List<LocalVideoData>> {
        try {
            val cursor = contentResolver.query(queryUri(), projection(), selection(), selectionArgs(), queryOrder())
            val videosList = mutableListOf<LocalVideoData>()
            var idx = 0
            if (cursor.moveToFirst()) {
                do {
                    videosList.add(LocalVideoData(
                        cursor.getString(cursor.getColumnIndex(VIDEO_NAME)),
                        cursor.getString(cursor.getColumnIndex(VIDEO_PATH)),
                        cursor.getString(cursor.getColumnIndex(VIDEO_TYPE)),
                        cursor.getLong(cursor.getColumnIndex(VIDEO_DURATION)),
                        cursor.getLong(cursor.getColumnIndex(VIDEO_SIZE)),
                        cursor.getLong(cursor.getColumnIndex(VIDEO_DATE_ADDED)),
                        cursor.getString(cursor.getColumnIndex(VIDEO_ALBUM)),
                        cursor.getString(cursor.getColumnIndex(VIDEO_RESOLUTION))))
                    idx++
                } while (cursor.moveToNext())
            }
            cursor.close()
            it.onSuccess(videosList)
        } catch (ex: Exception) {
            it.onError(ex)
        }
    }
}

PLAY THE VIDEO USING VIDEOVIEW

From your layout

<VideoView
    android:id="@+id/video_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    />
<!-- Should noted that width and height should 1 of them has fixed size, 
then we can easily set the ratio of video fram base on its size-->

From your Kotlin/Java code

private var uiHandler = Handler(Looper.getMainLooper())
private fun prepareVideo(videoPath: String) {
    video_view.setVideoPath(videoPath)
    video_view.setOnPreparedListener {
        // Enable your control after video is prepared
        btnPlay.isEnabled = true 
        btnPlay.safeClick(View.OnClickListener {
            handlePlayVideo()
        })
        //... 
        
        // Scaling the video image for keep its ratio
        it.setVideoScalingMode(MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT)

        // Set the first image to video view
        val mediaRetriever = MediaMetadataRetriever()
        mediaRetriever.setDataSource(videoPath)
        try {
            val bitmap = mediaRetriever.getFrameAtTime(video_view.currentPosition.toLong())
            video_view.background = BitmapDrawable(resources, bitmap)
        } catch (e: Exception) {
            // Hack or Trick if bitmap is not created
            video_view.seekTo(100)
        }

        // Handle for video length time and played time
        totalTime.text = video_view.duration // do format you want 00:00
        playedTime.text = video_view.currentPosition 
        seekbarPlay.max = video_view.duration
    }

    video_view.setOnCompletionListener {
        // reset Player and related UI when video is completed
        resetPlayer()
    }
    seekbarPlay.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                if (fromUser) {
                    video_view.seekTo(progress)
                }
            }

            override fun onStartTrackingTouch(seekBar: SeekBar?) {
                uiHandler.removeCallbacks(seekBarRunnable)
            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {
                uiHandler.post(seekBarRunnable)
                seekBar?.progress?.let {
                    video_view.seekTo(it)
                    playedTime.text = it.toLong().milliSecondToFullTime()
                }
            }
        })
}
private fun handlePlayVideo() {
    if (video_view.isPlaying) {
        handlePaused()
    } else {
        handlePlayed()
    }
}

override fun onPause() {
    if (video_view.isPlaying) handlePaused()
    super.onPause()
}

private fun handlePaused() {
    video_view.pause()
    uiHandler.removeCallbacksAndMessages(null)
}

private fun handlePlayed() {
    if (video_view.isPlaying) {
        video_view.resume()
    } else {
        video_view.seekTo(video_view.currentPosition)
        video_view.start()
        video_view.background = null // Remember to remove the first bitmap
    }

    uiHandler.post(seekBarRunnable)
    uiHandler.post(playedTimeRunnable)
}


private val seekBarRunnable = object : Runnable {
    override fun run() {
        if (!video_view.isPlaying) return
        seekbarPlay.progress = video_view.currentPosition
        uiHandler.postDelayed(this, SEEK_BAR_UPDATE_INTERVAL)

    }
}

private val playedTimeRunnable = object : Runnable {
    override fun run() {
        if (!video_view.isPlaying) return
        playedTime.text = video_view.currentPosition 
        uiHandler.postDelayed(this, PLAYED_TIME_UPDATE_INTERVAL)
    }
}