I am creating a music player using Exoplayer. If the user clicks on a specific track from recyclerview I want to play all tracks but switch to clicked index.
I am using ConcatenatingMediaSource to play the playlist.
val concatenatedSource = ConcatenatingMediaSource()
for (v in videos) {
when (v.type) {
"join" -> {
val mediaSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(Uri.parse(v.src))
concatenatedSource.addMediaSource(mediaSource)
}
"separate" -> {
val videoSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(Uri.parse(v.vSrc))
val audioSource = ProgressiveMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(Uri.parse(v.aSrc))
val mergedSource = MergingMediaSource(videoSource, audioSource)
concatenatedSource.addMediaSource(mergedSource)
}
"stream" -> {
val hlsSource = HlsMediaSource.Factory(cacheDataSourceFactory)
.createMediaSource(Uri.parse(v.src))
concatenatedSource.addMediaSource(hlsSource)
}
}
}
player.prepare(concatenatedSource)
Related
I am right now using FFMPEG to stream mp4 file using HLS.
I am using this link to enable encryption: https://hlsbook.net/how-to-encrypt-hls-video-with-ffmpeg/
To play video in my android app, I am using exoplayer, below is my source code to play video:
Player player;
private MediaSource buildMediaSource(Uri uri) {
TrackSelection.Factory adaptiveTrackSelection = new AdaptiveTrackSelection.Factory(new DefaultBandwidthMeter());
player = ExoPlayerFactory.newSimpleInstance(
this,
new DefaultTrackSelector(adaptiveTrackSelection));
playerView.setPlayer(player);
// These factories are used to construct two media sources.
DefaultBandwidthMeter defaultBandwidthMeter = DefaultBandwidthMeter.getSingletonInstance(this);
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(mContext,
Util.getUserAgent(mContext, "cookvid"), defaultBandwidthMeter);
//DataSource.Factory dataSourceFactory =
// new DefaultDataSourceFactory(this, "exoplayer-codelab");
HlsMediaSource.Factory mediaSourceFactory = new HlsMediaSource.Factory(dataSourceFactory);
return mediaSourceFactory.createMediaSource(uri);
//return new ProgressiveMediaSource.Factory(dataSourceFactory)
// .createMediaSource(uri);
}
private void initializePlayer() {
Uri uri = Uri.parse(getString(R.string.media_url_hls));
MediaSource mediaSource = buildMediaSource(uri);
player.setPlayWhenReady(playWhenReady);
player.seekTo(currentWindow, playbackPosition);
player.addListener(playbackStateListener);
player.prepare(mediaSource, false, false);
}
But with this code, I can not play video in app, If I am not using this encryption ,then exoplayer can play video without any issue.
Please help me on this, I am newbie on exoplayer side.
I created for you an example in Kotlin to make it work:
class MainActivity : AppCompatActivity() {
private var exoPlayer: SimpleExoPlayer? = null
private var trackSelector: DefaultTrackSelector? = null
var drmSessionManager: DefaultDrmSessionManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
trackSelector = DefaultTrackSelector(this)
exoPlayer = SimpleExoPlayer.Builder(this)
.setTrackSelector(trackSelector!!)
.build()
player_view.player = exoPlayer
var uri = Uri.fromFile( File("//android_asset/legend_enc.mp4"))
playVideo(uri, "zX65/4jzTK6wYYWwACTkwg", "Y8tfcYTdS2iaXF/xHuajKA")
}
private fun playVideo(url: Uri, id: String, value: String){
try {
drmSessionManager =
Util.getDrmUuid(C.CLEARKEY_UUID.toString())?.let { buildDrmSessionManager(
it,
true,
id,
value
) }
} catch (e: UnsupportedDrmException) {
e.printStackTrace()
}
exoPlayer?.setMediaSource(buildDashMediaSource(url))
exoPlayer?.prepare()
exoPlayer?.playWhenReady = true
}
private fun buildDashMediaSource(uri: Uri): MediaSource {
val dashChunkSourceFactory = DefaultDataSourceFactory(this, "agent")
return ProgressiveMediaSource.Factory(dashChunkSourceFactory)
.setDrmSessionManager(drmSessionManager ?: DrmSessionManager.DUMMY)
.createMediaSource(uri)
}
#Throws(UnsupportedDrmException::class)
private fun buildDrmSessionManager(uuid: UUID, multiSession: Boolean, id: String, value: String): DefaultDrmSessionManager {
val drmCallback = LocalMediaDrmCallback("{\"keys\":[{\"kty\":\"oct\",\"k\":\"${value}\",\"kid\":\"${id}\"}],\"type\":\"temporary\"}".toByteArray())
val mediaDrm = FrameworkMediaDrm.newInstance(uuid)
return DefaultDrmSessionManager(uuid, mediaDrm, drmCallback, null, multiSession)
}
}
In my app the user can select some videos from the gallery to edit them. After the user selects videos, I create a list of Uri and I want to play in exoplayer. I can play videos using their file path such as /storage/emulated/0/Android/data/com.vitor.videoeditor/files/Movies/VideoEditor/VID_20200722_133503.mp4, but I don't know how to do the same with a uri like content: // media / external / file / 305166.I tried to pass the uri directly to DataSpec () but it didn't work. What is the correct way to play the videos?
private fun playVideos(pathsList: List<String>) {
val concatenatingMediaSource = ConcatenatingMediaSource()
pathsList.forEach { filePath ->
val dataSpec = DataSpec(Uri.fromFile(File(filePath)))
val fileDataSource = FileDataSource()
try {
fileDataSource.open(dataSpec)
} catch (e: FileDataSource.FileDataSourceException) {
e.printStackTrace()
}
val dataSourceFactory = DataSource.Factory { fileDataSource }
val mediaSource: MediaSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(Uri.fromFile(File(filePath)))
concatenatingMediaSource.addMediaSource(mediaSource)
}
simpleExoPlayer = ExoPlayerFactory.newSimpleInstance(applicationContext)
with(simpleExoPlayer) {
prepare(concatenatingMediaSource)
playerView.player = simpleExoPlayer
playWhenReady = true
repeatMode = Player.REPEAT_MODE_ALL
}
}
content uri will expires soon So only during the file opening by intent you can play a video then it will expire. to avoid that
val cr = applicationContext.contentResolver
val flg: Int = Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
Intent.FLAG_GRANT_READ_URI_PERMISSION
cr.takePersistableUriPermission(uri!!, flg)
put above code in onActivity result. So that the uri will not expire.
and use same content uri later on.
here is two urls. First is "https://28api.haii.io/media/video/video_60_1.mp4",and Second is "https://28api.haii.io/media/video/video_70_1.mp4". When I load first video url, it load and play well. But when I load second url, the second video is broken. I can here only audio. So I tried comparing this two video by download.. but I can't understand the difference between this two url. Both are mp4 encoded video. But Exoplayer only load well at first but not at second.... how can I solve this... help me...
private fun initializePlayer() {
if (player == null) {
val trackSelector = DefaultTrackSelector()
trackSelector.setParameters(
trackSelector.buildUponParameters().setMaxVideoSizeSd()
)
player = ExoPlayerFactory.newSimpleInstance(context,trackSelector)
binding.videoPlayer.player = player
binding.videoPlayer.useController=false
binding.playerControl.player = binding.videoPlayer.player
mediaSource = mediaSourceFactory.createMediaSource(Uri.parse(mediaList[currentIndex].url))
player!!.prepare(mediaSource,false,false)
player!!.seekTo(currentWindow, playbackPosition)
player!!.playWhenReady = playWhenReady
setAudioFocus()
player!!.addListener(object : Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_IDLE -> {
}
Player.STATE_BUFFERING -> {
}
Player.STATE_READY -> {
}
Player.STATE_ENDED -> {
showPlayButton()
player!!.seekTo(currentWindow, 0)
showThumbnailImage()
player!!.playWhenReady = false
}
else -> {
}
}
}
})
}
}
private fun getVideoSource(url :String){
videoUrl = url
var mediaSource = mediaSourceFactory.createMediaSource(Uri.parse(url))
player!!.prepare(mediaSource)
playbackPosition=0
player!!.seekTo(currentWindow, playbackPosition)
player!!.playWhenReady = false
}
I implement exoplayer2 in my app for playing HLS Videos, but sometimes it plays just sound, and doesn't work correctly. What am I supposed to do? I couldn't find why this happens sometimes.
this is the code for initializing player :
fun initPalyer(){
val mainHandler = Handler()
val bandwidthMeter: BandwidthMeter = DefaultBandwidthMeter.Builder(context).build()
bandwidthMeter.addEventListener(mainHandler!!, this)
val trackSelectionFactory: TrackSelection.Factory = AdaptiveTrackSelection.Factory()
trackSelector = DefaultTrackSelector(context, trackSelectionFactory)
val builder = ParametersBuilder(context)
trackSelectorParameters = builder.build()
trackSelector!!.parameters = trackSelectorParameters
var rendersFactory: RenderersFactory = app.buildRenderersFactory(false)
player = SimpleExoPlayer.Builder(
context, renderersFactory
)
.setTrackSelector(trackSelector!!)
.setBandwidthMeter(bandwidthMeter)
//.setLoadControl(loadControl)
.build()
player!!.addListener(this)
loadState()
playerView.player = player!!
}
the code for preparing player :
private fun preparePlayer(uri: Uri) {
val mediaSource = MediaSourceBuilder().build(uri)
durationSet = false
player?.prepare(mediaSource, true, false)
}
and the code for creating mediaSources :
class MediaSourceBuilder {
//Build various MediaSource depending upon the type of Media for a given video/audio uri
fun build(uri: Uri): MediaSource {
val userAgent = PlayerConstants.USER_AGENT
val lastPath = uri.lastPathSegment?:""
val defaultHttpDataSourceFactory = DefaultHttpDataSourceFactory(userAgent)
if(lastPath.contains(PlayerConstants.FORMAT_MP3) || lastPath.contains(PlayerConstants.FORMAT_MP4)){
return ExtractorMediaSource.Factory(defaultHttpDataSourceFactory)
.createMediaSource(uri)
}else if(lastPath.contains(PlayerConstants.FORMAT_M3U8)){
return HlsMediaSource.Factory(defaultHttpDataSourceFactory)
.setAllowChunklessPreparation(true)
.createMediaSource(uri)
}else{
val dashChunkSourceFactory = DefaultDashChunkSource.Factory(defaultHttpDataSourceFactory)
return DashMediaSource.Factory(dashChunkSourceFactory, defaultHttpDataSourceFactory)
.createMediaSource(uri)
}
}
You might wanna change your DataSourceFactory, in case your URL are in HTTPS you might end up with an error, try using DefaultDataSourceFactory instead of DefaultHttpDataSourceFactory
I am using exoplayer in my application. I am using it to play video from a url. What i am trying to do is that i have three different urls for high,medium and low quality of same the video, and i would like to let the user to be able to change the video quality manually.
{
"lowQualityUrl":"string url",
"mediumQualityUrl":"string url",
"highQualityUrl":"string url"
}
In JWplayer there is an option to add different sources/url for different qualities. Is there something similar that can be done in exoplayer...?
Edit : I don't want to play videos one after another. I just want to switch to a different quality of the same video, like in youtube. But instead of using a single url for the source, what i have are 3 different urls for 3 qualities(low,medium,high) of the same video.
I found a solution or rather a workaround for the issue. I am using the exoplayer inside a recylcerview. This code might need some optimization.
I got this idea from another answer which was under this question,which had this github link, but i think the author deleted it.
What i did was create a class for keeping all the urls for a particular video. Then showing a Spinner above the exoplayer, and when user selects a particular quality then i prepare the exoplayer with the new URL, and then seekto to the previously playing position. You can ignore the StringUtils methods.
VideoPlayerConfig.kt
object VideoPlayerConfig {
//Minimum Video you want to buffer while Playing
val MIN_BUFFER_DURATION = 3000
//Max Video you want to buffer during PlayBack
val MAX_BUFFER_DURATION = 5000
//Min Video you want to buffer before start Playing it
val MIN_PLAYBACK_START_BUFFER = 1500
//Min video You want to buffer when user resumes video
val MIN_PLAYBACK_RESUME_BUFFER = 5000
}
VideoQuality.kt
You can change this class according to your need. I need to store exactly 3 urls for low,medium and high quality urls. And i needed to show them in that order as well in the spinner.
class VideoQuality {
private val videoQualityUrls = HashMap<String, String>()
companion object {
val LOW = getStringResource(R.string.low)
val MEDIUM = getStringResource(R.string.medium)
val HIGH = getStringResource(R.string.high)
}
val qualityArray
get() = arrayListOf<String>().apply {
if (hasQuality(LOW)) add(LOW)
if (hasQuality(MEDIUM)) add(MEDIUM)
if (hasQuality(HIGH)) add(HIGH)
}
var defaultVideoQuality: String? = HIGH
var lowQuality: String?
set(value) {
setVideoQualityUrl(LOW, value)
}
get() = videoQualityUrls[LOW] ?: ""
var mediumQuality: String?
set(value) {
setVideoQualityUrl(MEDIUM, value)
}
get() = videoQualityUrls[MEDIUM] ?: ""
var highQuality: String?
set(value) {
setVideoQualityUrl(HIGH, value)
}
get() = videoQualityUrls[HIGH] ?: ""
private fun setVideoQualityUrl(quality: String?, url: String?) {
if (url != null && quality != null) {
videoQualityUrls[quality] = url
}
}
private fun hasQuality(quality: String?): Boolean {
if (videoQualityUrls[quality] == null) {
return false
}
return true
}
fun getVideoQualityUrl(quality: String?): String? {
return videoQualityUrls[quality]
}
}
Methods to implement for the exoplayer
private fun initializePlayer() {
if (exoPlayer == null) {
val loadControl = DefaultLoadControl.Builder()
.setBufferDurationsMs(2 * VideoPlayerConfig.MIN_BUFFER_DURATION, 2 * VideoPlayerConfig.MAX_BUFFER_DURATION, VideoPlayerConfig.MIN_PLAYBACK_START_BUFFER, VideoPlayerConfig.MIN_PLAYBACK_RESUME_BUFFER)
.createDefaultLoadControl()
//Create a default TrackSelector
val videoTrackSelectionFactory = AdaptiveTrackSelection.Factory()
val trackSelector = DefaultTrackSelector(videoTrackSelectionFactory)
exoPlayer = ExoPlayerFactory.newSimpleInstance(itemView.context, DefaultRenderersFactory(itemView.context), trackSelector, loadControl)
exoPlayer!!.addListener(PlayEventListener())
val videoQualityInfo:VideoQuality = videoListVideoDataHolderData!!.videoQualityUrls //Just an object that i created and stored in a dataHolder for this view.
val url = videoQualityInfo.getVideoQualityUrl(videoQualityInfo.defaultVideoQuality) ?: ""
preparePlayer(url)
}
}
private fun preparePlayer(url: String) {
if (url.isNotEmpty()) {
val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(url))
exoPlayer?.prepare(mediaSource)
videoView.player = exoPlayer
} else {
Log.d(APPTAG, "NO DEFAULT URL")
}
}
private fun buildMediaSource(url: String): ProgressiveMediaSource {
val mUri: Uri = Uri.parse(url)
val dataSourceFactory = DefaultDataSourceFactory(
itemView.context,
Util.getUserAgent(itemView.context, getStringResource(R.string.app_name))
)
val videoSource = ProgressiveMediaSource.Factory(dataSourceFactory)
.createMediaSource(mUri)
return videoSource
}
And then in the Spinner/QualitySelector's OnItemSelectedListener
videoQualitySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onNothingSelected(parent: AdapterView<*>?) {
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
val currentTime = exoPlayer?.currentPosition
val isReadyToPlay = exoPlayer?.playWhenReady
val urlToBuild = when (videoQualityUrls.qualityArray[position]) {
VideoQuality.LOW -> videoQualityUrls.lowQuality
VideoQuality.MEDIUM -> videoQualityUrls.mediumQuality
else -> videoQualityUrls.highQuality
}
Log.d(APPTAG, "VIDEO DETAILS :::: ${currentTime} ${isReadyToPlay} ${urlToBuild}")
if (!urlToBuild.isNullOrEmpty()) {
val mediaSource = buildMediaSource(StringUtils.makeHttpUrl(urlToBuild))
exoPlayer?.prepare(mediaSource)
exoPlayer?.playWhenReady = isReadyToPlay ?: false
exoPlayer?.seekTo(currentTime ?: 0)
}
}
}