Browse Source

New module for video stats

merge-requests/509/head
Nicolas Pomepuy 6 years ago
parent
commit
080da58907
  1. 1
      live-plot-graph/.gitignore
  2. 62
      live-plot-graph/build.gradle
  3. 0
      live-plot-graph/consumer-rules.pro
  4. 21
      live-plot-graph/proguard-rules.pro
  5. 46
      live-plot-graph/src/androidTest/java/org/videolan/liveplotgraph/ExampleInstrumentedTest.kt
  6. 26
      live-plot-graph/src/main/AndroidManifest.xml
  7. 97
      live-plot-graph/src/main/java/org/videolan/liveplotgraph/LegendView.kt
  8. 44
      live-plot-graph/src/main/java/org/videolan/liveplotgraph/LineGraph.kt
  9. 253
      live-plot-graph/src/main/java/org/videolan/liveplotgraph/PlotView.kt
  10. 33
      live-plot-graph/src/main/res/values/attrs.xml
  11. 27
      live-plot-graph/src/main/res/values/strings.xml
  12. 41
      live-plot-graph/src/test/java/org/videolan/liveplotgraph/ExampleUnitTest.kt
  13. 3
      resources/src/main/res/values/strings.xml
  14. 2
      settings.gradle
  15. 1
      vlc-android/build.gradle
  16. 33
      vlc-android/res/drawable/ic_video_stats.xml
  17. 34
      vlc-android/res/layout/player_hud.xml
  18. 1
      vlc-android/res/values/attrs.xml
  19. 2
      vlc-android/res/values/colors.xml
  20. 3
      vlc-android/res/values/styles.xml
  21. 6
      vlc-android/src/org/videolan/vlc/gui/helpers/PlayerOptionsDelegate.kt
  22. 10
      vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt
  23. 98
      vlc-android/src/org/videolan/vlc/gui/video/VideoStatsDelegate.kt
  24. 5
      vlc-android/src/org/videolan/vlc/media/PlaylistManager.kt

1
live-plot-graph/.gitignore

@ -0,0 +1 @@
/build

62
live-plot-graph/build.gradle

@ -0,0 +1,62 @@
/*
* ************************************************************************
* build.gradle
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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.
* **************************************************************************
*
*
*/
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode rootProject.ext.versionCode
versionName rootProject.ext.versionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(':tools')
implementation "androidx.constraintlayout:constraintlayout:$rootProject.ext.constraintLayoutVersion"
}

0
live-plot-graph/consumer-rules.pro

21
live-plot-graph/proguard-rules.pro

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

46
live-plot-graph/src/androidTest/java/org/videolan/liveplotgraph/ExampleInstrumentedTest.kt

@ -0,0 +1,46 @@
/*
* ************************************************************************
* ExampleInstrumentedTest.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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.liveplotgraph
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("org.videolan.liveplotgraph.test", appContext.packageName)
}
}

26
live-plot-graph/src/main/AndroidManifest.xml

@ -0,0 +1,26 @@
<!--
~ *************************************************************************
~ AndroidManifest.xml
~ **************************************************************************
~ Copyright © 2020 VLC authors and VideoLAN
~ Author: Nicolas POMEPUY
~ 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.
~ ***************************************************************************
~
~
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.videolan.liveplotgraph" />

97
live-plot-graph/src/main/java/org/videolan/liveplotgraph/LegendView.kt

@ -0,0 +1,97 @@
/*
* ************************************************************************
* LegendView.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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.liveplotgraph
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.ViewGroup
import android.widget.GridLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import org.videolan.tools.dp
class LegendView : ConstraintLayout, PlotViewDataChangeListener {
private var plotViewId: Int = -1
private lateinit var plotView: PlotView
constructor(context: Context) : super(context) {
setWillNotDraw(false)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initAttributes(attrs, 0)
setWillNotDraw(false)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
initAttributes(attrs, defStyle)
setWillNotDraw(false)
}
private fun initAttributes(attrs: AttributeSet, defStyle: Int) {
attrs.let {
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.LPGPlotView, 0, defStyle)
try {
plotViewId = a.getResourceId(a.getIndex(R.styleable.LPGLegendView_lpg_plot_view), -1)
} catch (e: Exception) {
Log.w("", e.message, e)
} finally {
a.recycle()
}
}
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
plotView = (context as Activity).findViewById(plotViewId)
if (!::plotView.isInitialized) throw IllegalStateException("A valid plot view has to be provided")
plotView.addListener(this)
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
}
override fun onDataChanged(data: List<Pair<LineGraph, String>>) {
removeAllViews()
val grid = GridLayout(context)
grid.columnCount = 2
addView(grid)
data.forEach {
val title = TextView(context)
title.text = it.first.title
title.setTextColor(it.first.color)
grid.addView(title)
val value = TextView(context)
value.text = it.second
val layoutParams = GridLayout.LayoutParams()
layoutParams.leftMargin = 4.dp
value.layoutParams = layoutParams
grid.addView(value)
}
}
}

44
live-plot-graph/src/main/java/org/videolan/liveplotgraph/LineGraph.kt

@ -0,0 +1,44 @@
/*
* ************************************************************************
* LineGraph.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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.liveplotgraph
import android.graphics.Paint
import org.videolan.tools.dp
data class LineGraph(val index: Int, val title: String, val color: Int, val data: HashMap<Long, Float> = HashMap()) {
val paint: Paint by lazy {
val p = Paint()
p.color = color
p.strokeWidth = 2.dp.toFloat()
p.isAntiAlias = true
p.style = Paint.Style.STROKE
p
}
override fun equals(other: Any?): Boolean {
if (other is LineGraph && other.index == index) return true
return super.equals(other)
}
}

253
live-plot-graph/src/main/java/org/videolan/liveplotgraph/PlotView.kt

@ -0,0 +1,253 @@
/*
* ************************************************************************
* PlotView.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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.liveplotgraph
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.widget.FrameLayout
import org.videolan.tools.dp
import kotlin.math.log10
import kotlin.math.pow
import kotlin.math.round
class PlotView : FrameLayout {
private val textPaint: Paint by lazy {
val p = Paint()
p.color = color
p.textSize = 10.dp.toFloat()
p
}
val data = ArrayList<LineGraph>()
private val maxsY = ArrayList<Float>()
private val maxsX = ArrayList<Long>()
private val minsX = ArrayList<Long>()
private var color: Int = 0xFFFFFF
private var listeners = ArrayList<PlotViewDataChangeListener>()
constructor(context: Context) : super(context) {
setWillNotDraw(false)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initAttributes(attrs, 0)
setWillNotDraw(false)
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {
initAttributes(attrs, defStyle)
setWillNotDraw(false)
}
private fun initAttributes(attrs: AttributeSet, defStyle: Int) {
attrs.let {
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.LPGPlotView, 0, defStyle)
try {
color = a.getInt(R.styleable.LPGPlotView_lpg_color, 0xFFFFFF)
} catch (e: Exception) {
Log.w("", e.message, e)
} finally {
a.recycle()
}
}
}
fun addData(index: Int, value: Pair<Long, Float>) {
data.forEach { lineGraph ->
if (lineGraph.index == index) {
lineGraph.data[value.first] = value.second
if (lineGraph.data.size > 30) {
lineGraph.data.remove(lineGraph.data.toSortedMap().firstKey())
}
invalidate()
val listenerValue = ArrayList<Pair<LineGraph, String>>(data.size)
data.forEach { lineGraph ->
listenerValue.add(Pair(lineGraph, "${String.format("%.0f", lineGraph.data[lineGraph.data.keys.max()])} kb/s"))
}
listeners.forEach { it.onDataChanged(listenerValue) }
}
}
}
fun addListener(listener: PlotViewDataChangeListener) {
listeners.add(listener)
}
fun removeListener(listener: PlotViewDataChangeListener) {
listeners.remove(listener)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
maxsY.clear()
maxsX.clear()
minsX.clear()
data.forEach {
maxsY.add(it.data.maxBy { it.value }?.value ?: 0f)
}
val maxY = maxsY.max() ?: 0f
data.forEach {
maxsX.add(it.data.maxBy { it.key }?.key ?: 0L)
}
val maxX = maxsX.max() ?: 0L
data.forEach {
minsX.add(it.data.minBy { it.key }?.key ?: 0L)
}
val minX = minsX.min() ?: 0L
drawLines(maxY, minX, maxX, canvas)
drawGrid(canvas, maxY, minX, maxX)
}
private fun drawGrid(canvas: Canvas?, maxY: Float, minX: Long, maxX: Long) {
canvas?.let {
if (maxY <= 0F) return
// it.drawText("0", 10F, it.height.toFloat() - 2.dp, textPaint)
it.drawText("${String.format("%.0f", maxY)} kb/s", 10F, 10.dp.toFloat(), textPaint)
var center = maxY / 2
center = getRoundedByUnit(center)
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "Center: $center")
val centerCoord = measuredHeight * ((maxY - center) / maxY)
it.drawLine(0f, centerCoord, measuredWidth.toFloat(), centerCoord, textPaint)
it.drawText("${String.format("%.0f", center)} kb/s", 10F, centerCoord - 2.dp, textPaint)
//timestamps
var index = maxX - 1000
if (BuildConfig.DEBUG) Log.d(this::class.java.simpleName, "FirstIndex: $index")
while (index > minX) {
val xCoord = (measuredWidth * ((index - minX).toDouble() / (maxX - minX).toDouble())).toFloat()
it.drawLine(xCoord, 0F, xCoord, measuredHeight.toFloat() - 12.dp, textPaint)
val formattedText = "${String.format("%.0f", getRoundedByUnit((index - maxX).toFloat()) / 1000)}s"
it.drawText(formattedText, xCoord - (textPaint.measureText(formattedText) / 2), measuredHeight.toFloat(), textPaint)
index -= 1000
}
}
}
private fun getRoundedByUnit(number: Float): Float {
val lengthX = log10(number.toDouble()).toInt()
return (round(number / (10.0.pow(lengthX.toDouble()))) * (10.0.pow(lengthX.toDouble()))).toFloat()
}
private fun drawLines(maxY: Float, minX: Long, maxX: Long, canvas: Canvas?) {
data.forEach { line ->
var initialPoint: Pair<Float, Float>? = null
line.data.toSortedMap().forEach { point ->
if (initialPoint == null) {
initialPoint = getCoordinates(point, maxY, minX, maxX, measuredWidth, measuredHeight)
} else {
val currentPoint = getCoordinates(point, maxY, minX, maxX, measuredWidth, measuredHeight)
currentPoint.let {
canvas?.drawLine(initialPoint!!.first, initialPoint!!.second, it.first, it.second, line.paint)
initialPoint = it
}
}
}
}
}
// fun drawLines2(maxY: Float, minX: Long, maxX: Long, canvas: Canvas?) {
//
//
// data.forEach { line ->
// path.reset()
// val points = line.data.map {
// val coord = getCoordinates(it, maxY, minX, maxX, measuredWidth, measuredHeight)
// GraphPoint(coord.first, coord.second)
// }.sortedBy { it.x }
// for (i in points.indices) {
// val point = points[i]
// val smoothing = 100
// when (i) {
// 0 -> {
// val next: GraphPoint = points[i + 1]
// point.dx = (next.x - point.x) / smoothing
// point.dy = (next.y - point.y) / smoothing
// }
// points.size - 1 -> {
// val prev: GraphPoint = points[i - 1]
// point.dx = (point.x - prev.x) / smoothing
// point.dy = (point.y - prev.y) / smoothing
// }
// else -> {
// val next: GraphPoint = points[i + 1]
// val prev: GraphPoint = points[i - 1]
// point.dx = next.x - prev.x / smoothing
// point.dy = (next.y - prev.y) / smoothing
// }
// }
// }
// for (i in points.indices) {
// val point: GraphPoint = points[i]
// when {
// i == 0 -> {
// path.moveTo(point.x, point.y)
// }
// i < points.size - 1 -> {
// val prev: GraphPoint = points[i - 1]
// path.cubicTo(prev.x + prev.dx, prev.y + prev.dy, point.x - point.dx, point.y - point.dy, point.x, point.y)
// canvas?.drawCircle(point.x, point.y, 2.dp.toFloat(), line.paint)
// }
// else -> {
// path.lineTo(point.x, point.y)
// }
// }
// }
// canvas?.drawPath(path, line.paint)
// }
//
// }
private fun getCoordinates(point: Map.Entry<Long, Float>, maxY: Float, minX: Long, maxX: Long, measuredWidth: Int, measuredHeight: Int): Pair<Float, Float> = Pair((measuredWidth * ((point.key - minX).toDouble() / (maxX - minX).toDouble())).toFloat(), measuredHeight * ((maxY - point.value) / maxY))
fun clear() {
data.forEach {
it.data.clear()
}
}
fun addLine(lineGraph: LineGraph) {
if (!data.contains(lineGraph)) {
data.add(lineGraph)
}
}
}
data class GraphPoint(val x: Float, val y: Float) {
var dx: Float = 0F
var dy: Float = 0F
}
interface PlotViewDataChangeListener {
fun onDataChanged(data: List<Pair<LineGraph, String>>)
}

33
live-plot-graph/src/main/res/values/attrs.xml

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ *************************************************************************
~ attrs.xml
~ **************************************************************************
~ Copyright © 2020 VLC authors and VideoLAN
~ Author: Nicolas POMEPUY
~ 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.
~ ***************************************************************************
~
~
-->
<resources>
<declare-styleable name="LPGPlotView">
<attr name="lpg_color" format="reference|color" />
</declare-styleable>
<declare-styleable name="LPGLegendView">
<attr name="lpg_plot_view" format="reference" />
</declare-styleable>
</resources>

27
live-plot-graph/src/main/res/values/strings.xml

@ -0,0 +1,27 @@
<!--
~ *************************************************************************
~ strings.xml
~ **************************************************************************
~ Copyright © 2020 VLC authors and VideoLAN
~ Author: Nicolas POMEPUY
~ 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.
~ ***************************************************************************
~
~
-->
<resources>
<string name="app_name">live-plot-graph</string>
</resources>

41
live-plot-graph/src/test/java/org/videolan/liveplotgraph/ExampleUnitTest.kt

@ -0,0 +1,41 @@
/*
* ************************************************************************
* ExampleUnitTest.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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.liveplotgraph
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}

3
resources/src/main/res/values/strings.xml

@ -780,4 +780,7 @@
<string name="clear_media_db_warning">You will lose your progresses and the playlists you created.\n%s</string>
<string name="abrepeat_add_first_marker">Set start point</string>
<string name="abrepeat_add_second_marker">Set end point</string>
<string name="demux_bitrate">Demux Bitrate</string>
<string name="input_bitrate">Input bitrate</string>
<string name="video_stats">Video stats</string>
</resources>

2
settings.gradle

@ -1,3 +1,3 @@
include ':libvlc', ':api', ':medialibrary', ':tools', ':resources', ':mediadb'
include ':libvlc', ':api', ':medialibrary', ':tools', ':resources', ':mediadb', ':live-plot-graph'
include ':vlc-android'
include ':moviepedia'

1
vlc-android/build.gradle

@ -253,6 +253,7 @@ dependencies {
implementation project(':tools')
implementation project(':resources')
implementation project(':mediadb')
implementation project(':live-plot-graph')
// AppCompat
implementation "androidx.activity:activity-ktx:$rootProject.ext.androidxActivityVersion"

33
vlc-android/res/drawable/ic_video_stats.xml

@ -0,0 +1,33 @@
<!--
~ *************************************************************************
~ ic_video_stats.xml
~ **************************************************************************
~ Copyright © 2020 VLC authors and VideoLAN
~ Author: Nicolas POMEPUY
~ 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.
~ ***************************************************************************
~
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="36dp"
android:height="36dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/player_icon_color"
android:pathData="M16,6l2.29,2.29 -4.88,4.88 -4,-4L2,16.59 3.41,18l6,-6 4,4 6.3,-6.29L22,12V6z" />
</vector>

34
vlc-android/res/layout/player_hud.xml

@ -35,13 +35,45 @@
tools:theme="@style/Theme.VLC.TV"
tools:visibility="visible">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/stats_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/rounded_corners"
android:visibility="gone"
tools:visibility="visible"
android:padding="8dp"
vlc:layout_constraintStart_toStartOf="@+id/constraintLayout2"
vlc:layout_constraintEnd_toEndOf="@+id/constraintLayout2"
vlc:layout_constraintTop_toTopOf="parent">
<org.videolan.liveplotgraph.PlotView
android:id="@+id/plotView"
android:layout_width="250dp"
android:layout_height="140dp"
vlc:layout_constraintBottom_toBottomOf="parent"
vlc:layout_constraintStart_toStartOf="parent"
vlc:lpg_color="@color/white" />
<org.videolan.liveplotgraph.LegendView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="8dp"
vlc:layout_constraintBottom_toBottomOf="@+id/plotView"
vlc:layout_constraintStart_toEndOf="@+id/plotView"
vlc:layout_constraintTop_toTopOf="@+id/plotView"
vlc:lpg_plot_view="@+id/plotView" />
</androidx.constraintlayout.widget.ConstraintLayout>
<include
layout="@layout/ab_repeat_controls"
android:id="@+id/ab_repeat_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
vlc:layout_constraintStart_toStartOf="@+id/constraintLayout2" />
vlc:layout_constraintStart_toStartOf="@+id/constraintLayout2"
vlc:layout_constraintTop_toBottomOf="@id/stats_container" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraintLayout2"

1
vlc-android/res/values/attrs.xml

@ -72,6 +72,7 @@
<attr name="ic_abrepeat_reset" format="reference" />
<attr name="ic_abrepeat_seta" format="reference" />
<attr name="ic_abrepeat_setb" format="reference" />
<attr name="ic_video_stats" format="reference" />
<attr name="ic_crop_player" format="reference" />
<attr name="ic_dial" format="reference" />
<attr name="player_icon_color" format="reference" />

2
vlc-android/res/values/colors.xml

@ -74,4 +74,6 @@
<!--<color name="tv_card_content">#396375</color>-->
<color name="onboarding_grey_dark">#1c313a</color>
<color name="onboarding_grey">#455a64</color>
<color name="material_blue">#2196f3</color>
<color name="material_pink">#e91e63</color>
</resources>

3
vlc-android/res/values/styles.xml

@ -92,6 +92,7 @@
<item name="ic_passthrough">@drawable/ic_passthrough</item>
<item name="ic_abrepeat">@drawable/ic_abrepeat</item>
<item name="ic_abrepeat_reset">@drawable/ic_abrepeat_reset</item>
<item name="ic_video_stats">@drawable/ic_video_stats</item>
<item name="ic_dial">@drawable/ic_dial</item>
<item name="progress_background">@color/grey400</item>
<item name="ariane_text_color">@color/grey50</item>
@ -210,6 +211,7 @@
<item name="ic_passthrough">@drawable/ic_passthrough_w</item>
<item name="ic_abrepeat">@drawable/ic_abrepeat</item>
<item name="ic_abrepeat_reset">@drawable/ic_abrepeat_reset</item>
<item name="ic_video_stats">@drawable/ic_video_stats</item>
<item name="ic_dial">@drawable/ic_dial_w</item>
<item name="progress_indeterminate_tint">@color/orange500</item>
<item name="progress_background">@color/grey700</item>
@ -398,6 +400,7 @@
<item name="progress_background">@color/grey200</item>
<item name="ic_abrepeat">@drawable/ic_abrepeat</item>
<item name="ic_abrepeat_reset">@drawable/ic_abrepeat_reset</item>
<item name="ic_video_stats">@drawable/ic_video_stats</item>
<item name="ic_dial">@drawable/ic_dial_w</item>
<item name="ic_crop_player">@drawable/ic_crop_player</item>
<item name="android:colorControlHighlight" tools:targetApi="lollipop">@color/orange500</item>

6
vlc-android/src/org/videolan/vlc/gui/helpers/PlayerOptionsDelegate.kt

@ -59,6 +59,7 @@ private const val ID_SHUFFLE = 11
private const val ID_PASSTHROUGH = 12
private const val ID_ABREPEAT = 13
private const val ID_OVERLAY_SIZE = 14
private const val ID_VIDEO_STATS = 15
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
@ -116,6 +117,7 @@ class PlayerOptionsDelegate(val activity: AppCompatActivity, val service: Playba
if (service.canShuffle()) options.add(PlayerOption(playerOptionType, ID_SHUFFLE, R.drawable.ic_shuffle, res.getString(R.string.shuffle_title)))
val chaptersCount = service.getChapters(-1)?.size ?: 0
if (chaptersCount > 1) options.add(PlayerOption(playerOptionType, ID_CHAPTER_TITLE, R.attr.ic_chapter_normal_style, res.getString(R.string.go_to_chapter)))
options.add(PlayerOption(playerOptionType, ID_VIDEO_STATS, R.attr.ic_video_stats, res.getString(R.string.video_stats)))
}
options.add(PlayerOption(playerOptionType, ID_ABREPEAT, R.attr.ic_abrepeat, res.getString(R.string.ab_repeat)))
options.add(PlayerOption(playerOptionType, ID_SAVE_PLAYLIST, R.attr.ic_save, res.getString(R.string.playlist_save)))
@ -195,6 +197,10 @@ class PlayerOptionsDelegate(val activity: AppCompatActivity, val service: Playba
hide()
service.playlistManager.toggleABRepeat()
}
ID_VIDEO_STATS -> {
hide()
service.playlistManager.toggleStats()
}
else -> showFragment(option.id)
}
}

10
vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt

@ -193,6 +193,7 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
internal var fov: Float = 0.toFloat()
private var touchDelegate: VideoTouchDelegate? = null
private var statsDelegate: VideoStatsDelegate? = null
private var isTv: Boolean = false
// Tracks & Subtitles
@ -496,6 +497,7 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
val xRange = Math.max(dm.widthPixels, dm.heightPixels)
val sc = ScreenConfig(dm, xRange, yRange, currentScreenOrientation)
touchDelegate = VideoTouchDelegate(this, touch, sc, isTv)
statsDelegate = VideoStatsDelegate(this)
UiTools.setRotationAnimation(this)
if (savedInstanceState != null) {
savedTime = savedInstanceState.getLong(KEY_TIME)
@ -736,6 +738,7 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
previousMediaPath = null
addedExternalSubs.clear()
medialibrary.resumeBackgroundOperations()
statsDelegate?.stop()
}
private fun saveBrightness() {
@ -2135,6 +2138,13 @@ open class VideoPlayerActivity : AppCompatActivity(), IPlaybackSettingsControlle
manageAbRepeatStep()
})
service.playlistManager.videoStatsOn.observe(this, Observer {
if (it) showOverlay(true)
statsDelegate?.container = hudBinding.statsContainer
statsDelegate?.initPlotView(hudBinding.plotView)
if (it) statsDelegate?.start() else statsDelegate?.stop()
})
hudBinding.lifecycleOwner = this
val layoutParams = hudBinding.progressOverlay.layoutParams as RelativeLayout.LayoutParams
if (AndroidDevices.isPhone || !AndroidDevices.hasNavBar)

98
vlc-android/src/org/videolan/vlc/gui/video/VideoStatsDelegate.kt

@ -0,0 +1,98 @@
/*
* ************************************************************************
* VideoStatsDelegate.kt
* *************************************************************************
* Copyright © 2020 VLC authors and VideoLAN
* Author: Nicolas POMEPUY
* 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.gui.video
import android.annotation.SuppressLint
import android.os.Handler
import android.util.Log
import android.view.View
import android.widget.GridLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi
import org.videolan.libvlc.Media
import org.videolan.liveplotgraph.LineGraph
import org.videolan.liveplotgraph.PlotView
import org.videolan.vlc.BuildConfig
import org.videolan.vlc.R
@ExperimentalCoroutinesApi
@ObsoleteCoroutinesApi
class VideoStatsDelegate(private val player: VideoPlayerActivity) {
lateinit var container: ConstraintLayout
private var started = false
private val plotHandler: Handler = Handler()
private val firstTimecode = System.currentTimeMillis()
lateinit var plotView: PlotView
fun stop() {
started = false
plotHandler.removeCallbacks(runnable)
container.visibility = View.GONE
plotView.clear()
}
fun start() {
started = true
plotHandler.postDelayed(runnable, 300)
container.visibility = View.VISIBLE
}
fun initPlotView(plotView: PlotView) {
this.plotView = plotView
plotView.addLine(LineGraph(StatIndex.DEMUX_BITRATE.ordinal, player.getString(R.string.demux_bitrate), ContextCompat.getColor(player, R.color.material_blue)))
plotView.addLine(LineGraph(StatIndex.INPUT_BITRATE.ordinal, player.getString(R.string.input_bitrate), ContextCompat.getColor(player, R.color.material_pink)))
}
@SuppressLint("SetTextI18n")
private val runnable = Runnable {
val media = player.service?.mediaplayer?.media as? Media ?: return@Runnable
if (BuildConfig.DEBUG) Log.i(this::class.java.simpleName, "Stats: demuxBitrate: ${media.stats?.demuxBitrate} demuxCorrupted: ${media.stats?.demuxCorrupted} demuxDiscontinuity: ${media.stats?.demuxDiscontinuity} demuxReadBytes: ${media.stats?.demuxReadBytes}")
val now = System.currentTimeMillis() - firstTimecode
media.stats?.demuxBitrate?.let {
plotView.addData(StatIndex.DEMUX_BITRATE.ordinal, Pair(now, it * 8 * 1024))
}
media.stats?.inputBitrate?.let {
plotView.addData(StatIndex.INPUT_BITRATE.ordinal, Pair(now, it * 8 * 1024))
}
media.let {
for (i in 0 until it.trackCount) {
val grid = GridLayout(player)
grid.columnCount = 2
}
}
if (started) {
start()
}
}
}
enum class StatIndex {
INPUT_BITRATE, DEMUX_BITRATE
}

5
vlc-android/src/org/videolan/vlc/media/PlaylistManager.kt

@ -71,6 +71,7 @@ class PlaylistManager(val service: PlaybackService) : MediaWrapperList.EventList
private var entryUrl : String? = null
val abRepeat by lazy(LazyThreadSafetyMode.NONE) { MutableLiveData<ABRepeat>().apply { value = ABRepeat() } }
val abRepeatOn by lazy(LazyThreadSafetyMode.NONE) { MutableLiveData<Boolean>().apply { value = false } }
val videoStatsOn by lazy(LazyThreadSafetyMode.NONE) { MutableLiveData<Boolean>().apply { value = false } }
private val mediaFactory = FactoryManager.getFactory(IMediaFactory.factoryId) as IMediaFactory
@ -720,6 +721,10 @@ class PlaylistManager(val service: PlaybackService) : MediaWrapperList.EventList
}
}
fun toggleStats() {
videoStatsOn.value = !videoStatsOn.value!!
}
fun clearABRepeat() {
abRepeat.value = abRepeat.value?.apply {
start = -1L

Loading…
Cancel
Save