24 changed files with 847 additions and 2 deletions
@ -0,0 +1 @@ |
|||
/build |
|||
@ -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,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 |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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" /> |
|||
@ -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) |
|||
} |
|||
} |
|||
} |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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>>) |
|||
} |
|||
@ -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> |
|||
@ -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> |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -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' |
|||
|
|||
@ -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> |
|||
@ -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 |
|||
} |
|||
Loading…
Reference in new issue