Skip to main content

Video Player Settings

AdVideoPlayer Implementation

The Player in which the video advertisement will be played is not provided by the SDK. It is rather injected into the Player used by the service.

For this process, the service player must implement the AdVideoPlayer interface. (com.naver.gfpsdk.provider.AdVideoPlayer)

info

The example below is when the exo player is used.

When applying an actual video ad, it must be implemented with the player used by the service.

dependencies {
implementation(platform("com.naver.gfpsdk:nam-bom:7.5.3"))
implementation("com.naver.gfpsdk:nam-core")

implementation("com.google.android.exoplayer:exoplayer-core:2.17.0")
implementation("com.google.android.exoplayer:exoplayer-hls:2.17.0")
implementation("com.google.android.exoplayer:exoplayer-ui:2.17.0")
}
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.util.Log
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.source.LoadEventInfo
import com.google.android.exoplayer2.source.MediaLoadData
import com.google.android.exoplayer2.source.MediaSource
import com.google.android.exoplayer2.source.MediaSourceEventListener
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.upstream.DefaultDataSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.naver.gfpsdk.provider.AdVideoPlayer
import java.io.IOException
import java.util.concurrent.CopyOnWriteArraySet

class SampleExoPlayerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : StyledPlayerView(context, attrs, defStyleAttr) {

private val LOG_TAG = SampleExoPlayerView::class.java.simpleName
private val videoPlayerCallbacks = CopyOnWriteArraySet<AdVideoPlayer.PlayerCallback>()
private var player: ExoPlayer
private var playbackState: PlaybackState
private var adPlayer: AdVideoPlayer
private var contentVideoUrl: String? = null
private var savedContentPosition = 0L
private var bitrateChangeListener: VideoAdMediaFormatChangeListener? = null

init {
player = ExoPlayer.Builder(context).build()
player.addListener(
object: Player.Listener {
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_ENDED && playbackState == PlaybackState.PLAYING) {
adPlayer.disablePlaybackControls()
player.stop()
player.clearMediaItems()
playbackState = PlaybackState.STOPPED

videoPlayerCallbacks.forEach { it.onCompleted() }

adPlayer.enablePlaybackControls()
}
}

override fun onPlayerError(e: PlaybackException) {
Log.e(LOG_TAG, "onPlayerError", e)

videoPlayerCallbacks.forEach { it.onError() }
}
}
)

setShowBuffering(SHOW_BUFFERING_WHEN_PLAYING)
useController = false
player.playWhenReady = true
setPlayer(player)

playbackState = PlaybackState.STOPPED

adPlayer = object: AdVideoPlayer {
override fun play() {
player.playWhenReady = true
playbackState = PlaybackState.PLAYING

videoPlayerCallbacks.forEach { it.onPlay() }
}

override fun pause() {
player.playWhenReady = false
playbackState = PlaybackState.PAUSED

videoPlayerCallbacks.forEach { it.onPause() }
}

override fun resume() {
player.playWhenReady = true
playbackState = PlaybackState.PLAYING

videoPlayerCallbacks.forEach { it.onResume() }
}

override fun getCurrentPosition(): Long {
return player.currentPosition
}

override fun seekTo(videoPosition: Long) {
player.seekTo(videoPosition)
}

override fun getDuration(): Long {
return if (playbackState == PlaybackState.STOPPED || player == null) 0L else player.duration
}

override fun getBufferedTime(): Int {
return if (playbackState == PlaybackState.STOPPED || player == null) 0 else
(player.duration * player.bufferedPercentage / 100).toInt()
}

override fun getVolume(): Float {
return player.volume
}

override fun stopPlayback() {
if (playbackState == PlaybackState.STOPPED) {
return
}

if (isPlaying && player != null) {
player.stop()
}

playbackState = PlaybackState.STOPPED
}

override fun disablePlaybackControls() {
// nothing
}

override fun enablePlaybackControls() {
// nothing
}

override fun setVideoPath(videoUrl: String) {
player.setMediaSource(generateMediaSource(videoUrl), true)
player.prepare()
}

override fun addPlayerCallback(callback: AdVideoPlayer.PlayerCallback) {
videoPlayerCallbacks.add(callback)
}

override fun removePlayerCallback(callback: AdVideoPlayer.PlayerCallback) {
videoPlayerCallbacks.remove(callback)
}
}
adPlayer.enablePlaybackControls()
}

fun createAdVideoPlayer(contentVideoUrl: String): AdVideoPlayer {
this.contentVideoUrl = contentVideoUrl
initAdVideoPlayer()
return adPlayer
}

fun initAdVideoPlayer() {
playbackState = PlaybackState.STOPPED
adPlayer = object: AdVideoPlayer {
override fun play() {
player?.let { it.playWhenReady = true }

playbackState = PlaybackState.PLAYING

videoPlayerCallbacks.forEach { it.onPlay() }
}

override fun pause() {
player?.let { it.playWhenReady = false }

playbackState = PlaybackState.PAUSED

videoPlayerCallbacks.forEach { it.onPause() }
}

override fun resume() {
player?.let { it.playWhenReady = true }

playbackState = PlaybackState.PLAYING

videoPlayerCallbacks.forEach { it.onResume() }
}

override fun getCurrentPosition(): Long {
return if (player != null) {
player.currentPosition
} else {
0
}
}

override fun seekTo(videoPosition: Long) {
player?.let { it.seekTo(videoPosition) }
}

override fun getDuration(): Long {
return if (playbackState == PlaybackState.STOPPED || player == null) {
0
} else {
player.duration
}
}

override fun getBufferedTime(): Int {
return if (playbackState == PlaybackState.STOPPED || player == null) {
0
} else {
(player.duration * player.bufferedPercentage / 100).toInt()
}
}

override fun getVolume(): Float {
return if (player != null) {
player.volume
} else {
0f
}
}

override fun stopPlayback() {
if (playbackState == PlaybackState.STOPPED) {
return
}

if (isPlaying && player != null) {
player.stop()
}

playbackState = PlaybackState.STOPPED
}

override fun disablePlaybackControls() {
// nothing
}

override fun enablePlaybackControls() {
// nothing
}

override fun setVideoPath(videoUrl: String) {
player?.let {
it.setMediaSource(generateMediaSource(videoUrl), true)
it.prepare()
}
}

override fun addPlayerCallback(callback: AdVideoPlayer.PlayerCallback) {
videoPlayerCallbacks.add(callback)
}

override fun removePlayerCallback(callback: AdVideoPlayer.PlayerCallback) {
videoPlayerCallbacks.remove(callback)
}
}
adPlayer.enablePlaybackControls()
}

fun pauseContentsRequest() {
savedContentPosition = adPlayer.currentPosition
adPlayer.stopPlayback()
useController = false
}

fun resumeContentsRequest() {
useController = true
player?.let {
it.setMediaSource(generatedMediaSource(contentVideoUrl), true)
it.prepare()
adPlayer.seekTo(savedContentPosition)
adPlayer.play()
}
}

private fun generateMediaSource(videoUrl: String?): MediaSource {
return if (videoUrl != null && videoUrl.toLowerCase().contains(".m3u8")) {
val hlsMediaSource = HlsMediaSource.Factory(
DefaultDataSource.Factory(
context,
DefaultHttpDataSource.Factory().setUserAgent("user_agent")
)
).createMediaSource(MediaItem.fromUri(videoUrl))

hlsMediaSource.apply {
addEventListener(
Handler(Looper.getMainLooper()),
object: MediaSourceEventListener {
override fun onLoadStarted(
windowIndex: Int,
mediaPeriodId: MediaSource.MediaPeriodId?,
loadEventInfo: LoadEventInfo,
mediaLoadData: MediaLoadData
) {
// do nothing
}

override fun onLoadCompleted(
windowIndex: Int,
mediaPeriodId: MediaSource.MediaPeriodId?,
loadEventInfo: LoadEventInfo,
mediaLoadData: MediaLoadData
) {
// do nothing
}

override fun onLoadCanceled(
windowIndex: Int,
mediaPeriodId: MediaSource.MediaPeriodId?,
loadEventInfo: LoadEventInfo,
mediaLoadData: MediaLoadData
) {
// do nothing
}

override fun onLoadError(
windowIndex: Int,
mediaPeriodId: MediaSource.MediaPeriodId?,
loadEventInfo: LoadEventInfo,
mediaLoadData: MediaLoadData,
error: IOException,
wasCanceled: Boolean
) {
// do nothing
}

override fun onUpstreamDiscarded(
windowIndex: Int,
mediaPeriodId: MediaSource.MediaPeriodId,
mediaLoadData: MediaLoadData
) {
// do nothing
}

override fun onDownstreamFormatChanged(
windowIndex: Int,
mediaPeriodId: MediaSource.MediaPeriodId?,
mediaLoadData: MediaLoadData
) {
if (bitrateChangeListener != null && mediaLoadData.trackFormat != null) {
bitrateChangeListener?.onMediaFormatChanged(
mediaLoadData.trackFormat.bitrate / 1000,
mediaLoadData.trackFormat.containerMimeType
)
}
}
}
)
}
} else {
ProgressiveMediaSource.Factory(
DefaultDataSource.Factory(
context,
DefaultHttpDataSource.Factory().setUserAgent("user_agent")
)
).createMediaSource(MediaItem.fromUri(videoUrl))
}
}

fun reset() {
savedContentPosition = 0

player?.let {
it.stop()
it.clearMediaItems()
}
}

fun release() {
if (player != null) {
player.release()
player = null
}

adPlayer = null
videoPlayerCallbacks.clear()
removeAllViews()
}

val isPlaying: Boolean
get() = playbackState == PlaybackState.PLAYING

val isPaused: Boolean
get() = playbackState == PlaybackState.PAUSED

fun addAdBitrateChangeListener(listener: VideoAdMediaFormatChangeListener) {
bitrateChangeListener = listener
}

interface VideoAdMediaFormatChangeListener {
fun onMediaFormatChanged(bitrate: Int, mimeType: String)
}

enum class PlaybackState {
STOPPED, PAUSED, PLAYING
}
}