TvChannels.kt 6.31 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*****************************************************************************
 * TvChannels.kt
 *****************************************************************************
 * Copyright © 2018 VLC authors, VideoLAN and VideoLabs
 * Author: Geoffrey Métais
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
package org.videolan.vlc.util

23
import android.content.ComponentName
24
import android.content.Context
25
import android.database.Cursor
26 27 28 29
import android.net.Uri
import android.os.Build
import android.support.annotation.RequiresApi
import android.support.media.tv.TvContractCompat
30 31 32
import android.support.media.tv.WatchNextProgram
import android.text.TextUtils
import android.util.Log
33 34 35 36 37
import kotlinx.coroutines.experimental.CoroutineStart
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.withContext
import org.videolan.medialibrary.Medialibrary
import org.videolan.medialibrary.media.MediaWrapper
38
import org.videolan.vlc.*
39 40 41 42
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
import videolan.org.commontools.*

43

44 45 46 47 48 49
private const val TAG = "VLC/TvChannels"
private const val MAX_RECOMMENDATIONS = 3

@RequiresApi(Build.VERSION_CODES.O)
fun setChannel(context: Context) = launch(start = CoroutineStart.UNDISPATCHED) {
    val channelId = withContext(VLCIO) {
50
        val prefs = Settings.getInstance(context)
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
        val name = context.getString(R.string.tv_my_new_videos)
        createOrUpdateChannel(prefs, context, name, R.drawable.icon, BuildConfig.APPLICATION_ID)
    }
    if (Permissions.canReadStorage(context)) updatePrograms(context, channelId)
}

suspend fun updatePrograms(context: Context, channelId: Long) {
    if (channelId == -1L) return
    val ml = Medialibrary.getInstance()
    if (!ml.isStarted) {
        ml.addOnMedialibraryReadyListener(object : Medialibrary.OnMedialibraryReadyListener {
            override fun onMedialibraryIdle() {}
            override fun onMedialibraryReady() {
                ml.removeOnMedialibraryReadyListener(this)
                launch { updatePrograms(context, channelId) }
            }

        })
        context.startMedialibrary(false, false, false)
        return
    }
    val programs = withContext(VLCIO) { existingPrograms(context, channelId) }
    val videoList = withContext(VLCIO) { VLCApplication.getMLInstance().recentVideos }
    if (Util.isArrayEmpty(videoList)) return
75
    val cn = ComponentName(context, PreviewVideoInputService::class.java)
76 77 78 79 80 81 82
    for ((count, mw) in videoList.withIndex()) {
        if (mw == null) continue
        val index = programs.indexOfId(mw.id)
        if (index != -1) {
            programs.removeAt(index)
            continue
        }
83
        val desc = ProgramDesc(channelId, mw.id, mw.title, mw.description,
84 85
                mw.artUri(), mw.length.toInt(), mw.time.toInt(),
                mw.width, mw.height, BuildConfig.APPLICATION_ID)
86 87 88 89
        val program = buildProgram(cn, desc)
        launch(VLCIO) {
            context.contentResolver.insert(TvContractCompat.PreviewPrograms.CONTENT_URI, program.toContentValues())
        }
90 91 92 93 94 95 96
        if (count - programs.size >= MAX_RECOMMENDATIONS) break
    }
    for (program in programs) {
        withContext(VLCIO) { context.contentResolver.delete(TvContractCompat.buildPreviewProgramUri(program.programId), null, null) }
    }
}

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
fun setResumeProgram(context: Context, mw: MediaWrapper) {
    var cursor: Cursor? = null
    var isProgramPresent = false
    try {
        cursor = context.contentResolver.query(
                TvContractCompat.WatchNextPrograms.CONTENT_URI, WATCH_NEXT_MAP_PROJECTION, null,
                null, null)
        cursor?.let {
            while (it.moveToNext()) {
                if (!it.isNull(1) && TextUtils.equals(mw.id.toString(), cursor.getString(1))) {
                    // Found a row that contains the matching ID
                    val watchNextProgramId = cursor.getLong(0)
                    if (it.getInt(2) == 0 || mw.time == 0L) { //Row removed by user or progress null
                        if (deleteWatchNext(context, watchNextProgramId) < 1) {
                            Log.e(TAG, "Delete program failed")
                            return
                        }
                    } else { // Update the program
                        val existingProgram = WatchNextProgram.fromCursor(cursor)
                        updateWatchNext(context, existingProgram, mw.time, watchNextProgramId)
                        isProgramPresent = true
                    }
                    break
                }
            }
        }
        if (!isProgramPresent && mw.time != 0L) {
124
            val desc = ProgramDesc(0L, mw.id, mw.title, mw.description,
125 126
                    mw.artUri(), mw.length.toInt(), mw.time.toInt(),
                    mw.width, mw.height, BuildConfig.APPLICATION_ID)
127 128
            val cn = ComponentName(context, PreviewVideoInputService::class.java)
            val program = buildWatchNextProgram(cn, desc)
129 130 131 132 133 134 135 136 137
            val watchNextProgramUri = context.contentResolver.insert(TvContractCompat.WatchNextPrograms.CONTENT_URI, program.toContentValues())
            if (watchNextProgramUri == null || watchNextProgramUri == Uri.EMPTY) Log.e(TAG, "Insert watch next program failed")
        }
    } finally {
        cursor?.close()
    }

}

138 139 140 141 142 143 144 145
private fun MediaWrapper.artUri() : Uri {
    val mrl = artworkMrl ?: return Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/${R.drawable.ic_browser_video_big_normal}")
    return try {
        getFileUri(mrl)
    } catch (ex: IllegalArgumentException) {
        Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/${R.drawable.ic_browser_video_big_normal}")
    }
}