Skip to content

Commit

Permalink
MPV
Browse files Browse the repository at this point in the history
  • Loading branch information
CarlosOlivo committed Dec 1, 2021
1 parent c123be1 commit 28e173b
Show file tree
Hide file tree
Showing 23 changed files with 1,986 additions and 53 deletions.
15 changes: 15 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ android {
}
}

/*splits {
abi {
isEnable = true
isUniversalApk = true
}
}*/

@Suppress("UnstableApiUsage")
buildFeatures {
viewBinding = true
Expand All @@ -89,6 +96,9 @@ android {
compileOptions {
isCoreLibraryDesugaringEnabled = true
}
packagingOptions {
jniLibs.keepDebugSymbols += "**/*.so"
}
lint {
isAbortOnError = false
sarifReport = true
Expand Down Expand Up @@ -163,6 +173,11 @@ dependencies {
testImplementation(libs.bundles.kotest)
testImplementation(libs.mockk)
androidTestImplementation(libs.bundles.androidx.test)

// TODO: Decide how to build / publish this..
implementation(files("libmpv\\app-release.aar"))
//implementation(files("libmpv\\app-sources.jar"))
//implementation(files("libmpv\\app-javadoc.jar"))
}

tasks {
Expand Down
Binary file added app/libmpv/app-javadoc.jar
Binary file not shown.
Binary file added app/libmpv/app-release.aar
Binary file not shown.
Binary file added app/libmpv/app-sources.jar
Binary file not shown.
13 changes: 13 additions & 0 deletions app/src/main/assets/mpv.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# hwdec: try to use hardware decoding
hwdec=mediacodec-copy
hwdec-codecs="h264,hevc,mpeg4,mpeg2video,vp8,vp9"
gpu-dumb-mode=auto
# tls: allow self signed certificate
tls-verify=no
tls-ca-file=""
# demuxer: limit cache to 32 MiB, the default is too high for mobile devices
demuxer-max-bytes=32MiB
demuxer-max-back-bytes=32MiB
# sub: scale subtitles with video
sub-scale-with-window=no
sub-use-margins=no
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export class ExoPlayerPlugin {
export class NativePlayerPlugin {
constructor({ events, playbackManager, loading }) {
window['ExoPlayer'] = this;

this.events = events;
this.playbackManager = playbackManager;
this.loading = loading;

this.name = 'ExoPlayer';
this.name = 'NativePlayer';
this.type = 'mediaplayer';
this.id = 'exoplayer';
this.id = 'nativeplayer';

// Prioritize first
this.priority = -1;
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/assets/native/nativeshell.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const features = [

const plugins = [
'NavigationPlugin',
'ExoPlayerPlugin',
'NativePlayerPlugin',
'ExternalPlayerPlugin'
];

Expand Down
159 changes: 159 additions & 0 deletions app/src/main/assets/skip-intro.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
MAX_SPEED = 100
ONE_SECOND = 1
skip = false
ov = mp.create_osd_overlay("ass-events")
ov.data = "Seeking..."
-- Max noise (dB) and min silence duration (s) to trigger
opts = { quietness = -30, duration = 0.5 }


function setOptions()
local options = require 'mp.options'
options.read_options(opts)
end

function setTime(time)
mp.set_property_number('time-pos', time)
end

function getTime()
return mp.get_property_native('time-pos')
end

function setSpeed(speed)
mp.set_property('speed', speed)
end

function getSpeed()
return mp.get_property('speed')
end

function setPause(state)
mp.set_property_bool('pause', state)
end

function setMute(state)
mp.set_property_bool('mute', state)
end

function initAudioFilter()
local af_table = mp.get_property_native('af')
af_table[#af_table + 1] = {
enabled = false,
label = 'silencedetect',
name = 'lavfi',
params = { graph = 'silencedetect=noise=' .. opts.quietness .. 'dB:d=' .. opts.duration }
}
mp.set_property_native('af', af_table)
end

function initVideoFilter()
local vf_table = mp.get_property_native('vf')
vf_table[#vf_table + 1] = {
enabled = false,
label = 'blackout',
name = 'lavfi',
params = { graph = '' }
}
mp.set_property_native('vf', vf_table)
end

function setAudioFilter(state)
local af_table = mp.get_property_native('af')
if #af_table > 0 then
for i = #af_table, 1, -1 do
if af_table[i].label == 'silencedetect' then
af_table[i].enabled = state
mp.set_property_native('af', af_table)
break
end
end
end
end

function dim(state)
local dim = { width = 0, height = 0 }
if state == true then
dim.width = mp.get_property_native('width')
dim.height = mp.get_property_native('height')
end
return dim.width .. 'x' .. dim.height
end

function setVideoFilter(state)
local vf_table = mp.get_property_native('vf')
if #vf_table > 0 then
for i = #vf_table, 1, -1 do
if vf_table[i].label == 'blackout' then
vf_table[i].enabled = state
vf_table[i].params = { graph = 'nullsink,color=c=black:s=' .. dim(state) }
mp.set_property_native('vf', vf_table)
break
end
end
end
end

function silenceTrigger(name, value)
if value == '{}' or value == nil then
return
end

local skipTime = tonumber(string.match(value, '%d+%.?%d+'))
local currTime = getTime()

if skipTime == nil or skipTime < currTime + ONE_SECOND then
return
end

stopSkip()
setTime(skipTime)
skip = false
end

function setAudioTrigger(state)
if state == true then
mp.observe_property('af-metadata/silencedetect', 'string', silenceTrigger)
else
mp.unobserve_property(silenceTrigger)
end
end

function startSkip()
ov:update()
startTime = getTime()
startSpeed = getSpeed()
-- This audio filter detects moments of silence
setAudioFilter(true)
-- This video filter makes fast-forward faster
setVideoFilter(true)
setAudioTrigger(true)
setPause(false)
setMute(true)
setSpeed(MAX_SPEED)
end

function stopSkip()
ov:remove()
setAudioFilter(false)
setVideoFilter(false)
setAudioTrigger(false)
setMute(false)
setSpeed(startSpeed)
end

function keypress()
skip = not skip
if skip then
startSkip()
else
stopSkip()
setTime(startTime)
end
end

setOptions(opts)
initAudioFilter()
initVideoFilter()

mp.add_key_binding(nil, 'skip-key', keypress)
7 changes: 5 additions & 2 deletions app/src/main/java/org/jellyfin/mobile/ApplicationModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import kotlinx.coroutines.channels.Channel
import okhttp3.OkHttpClient
import org.jellyfin.mobile.api.DeviceProfileBuilder
import org.jellyfin.mobile.bridge.ExternalPlayer
import org.jellyfin.mobile.bridge.NativePlayer
import org.jellyfin.mobile.controller.ApiController
import org.jellyfin.mobile.fragment.ConnectFragment
import org.jellyfin.mobile.fragment.WebViewFragment
import org.jellyfin.mobile.media.car.LibraryBrowser
import org.jellyfin.mobile.player.PlayerEvent
import org.jellyfin.mobile.player.PlayerFragment
import org.jellyfin.mobile.player.mpv.MPVPlayer
import org.jellyfin.mobile.player.source.MediaSourceResolver
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.PermissionRequestHelper
Expand Down Expand Up @@ -61,8 +63,9 @@ val applicationModule = module {
// Media player helpers
single { MediaSourceResolver(get()) }
single { DeviceProfileBuilder() }
single { get<DeviceProfileBuilder>().getDeviceProfile() }
single(named(ExternalPlayer.DEVICE_PROFILE_NAME)) { get<DeviceProfileBuilder>().getExternalPlayerProfile() }
single(named(ExternalPlayer.PLAYER_NAME)) { get<DeviceProfileBuilder>().getExternalPlayerProfile() }
single(named(MPVPlayer.PLAYER_NAME)) { get<DeviceProfileBuilder>().getMPVPlayerProfile() }
single(named(NativePlayer.PLAYER_NAME)) { get<DeviceProfileBuilder>().getExoPlayerProfile() }

// ExoPlayer factories
single<DataSource.Factory> {
Expand Down
83 changes: 77 additions & 6 deletions app/src/main/java/org/jellyfin/mobile/api/DeviceProfileBuilder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package org.jellyfin.mobile.api

import android.media.MediaCodecList
import org.jellyfin.mobile.bridge.ExternalPlayer
import org.jellyfin.mobile.bridge.NativePlayer
import org.jellyfin.mobile.player.DeviceCodec
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.player.mpv.MPVPlayer
import org.jellyfin.sdk.model.api.CodecProfile
import org.jellyfin.sdk.model.api.ContainerProfile
import org.jellyfin.sdk.model.api.DeviceProfile
Expand All @@ -21,7 +22,7 @@ class DeviceProfileBuilder {
require(SUPPORTED_CONTAINER_FORMATS.size == AVAILABLE_VIDEO_CODECS.size && SUPPORTED_CONTAINER_FORMATS.size == AVAILABLE_AUDIO_CODECS.size)
}

fun getDeviceProfile(): DeviceProfile {
fun getExoPlayerProfile(): DeviceProfile {
val containerProfiles = ArrayList<ContainerProfile>()
val directPlayProfiles = ArrayList<DirectPlayProfile>()
val codecProfiles = ArrayList<CodecProfile>()
Expand Down Expand Up @@ -66,9 +67,9 @@ class DeviceProfileBuilder {
}

return DeviceProfile(
name = Constants.APP_INFO_NAME,
name = NativePlayer.PLAYER_NAME,
directPlayProfiles = directPlayProfiles,
transcodingProfiles = getTranscodingProfiles(),
transcodingProfiles = getExoPlayerTranscodingProfiles(),
containerProfiles = containerProfiles,
codecProfiles = codecProfiles,
subtitleProfiles = getSubtitleProfiles(EXO_EMBEDDED_SUBTITLES, EXO_EXTERNAL_SUBTITLES),
Expand All @@ -87,8 +88,32 @@ class DeviceProfileBuilder {
)
}

fun getMPVPlayerProfile(): DeviceProfile = DeviceProfile(
name = MPVPlayer.PLAYER_NAME,
directPlayProfiles = listOf(
DirectPlayProfile(type = DlnaProfileType.VIDEO),
DirectPlayProfile(type = DlnaProfileType.AUDIO),
),
transcodingProfiles = getMPVTranscodingProfiles(),
containerProfiles = emptyList(),
codecProfiles = emptyList(),
subtitleProfiles = getSubtitleProfiles(MPV_PLAYER_SUBTITLES.plus("vtt"), MPV_PLAYER_SUBTITLES),

// TODO: remove redundant defaults after API/SDK is fixed
maxAlbumArtWidth = Int.MAX_VALUE,
maxAlbumArtHeight = Int.MAX_VALUE,
timelineOffsetSeconds = 0,
enableAlbumArtInDidl = false,
enableSingleAlbumArtLimit = false,
enableSingleSubtitleLimit = false,
requiresPlainFolders = false,
requiresPlainVideoItems = false,
enableMsMediaReceiverRegistrar = false,
ignoreTranscodeByteRangeRequests = false,
)

fun getExternalPlayerProfile(): DeviceProfile = DeviceProfile(
name = ExternalPlayer.DEVICE_PROFILE_NAME,
name = ExternalPlayer.PLAYER_NAME,
directPlayProfiles = listOf(
DirectPlayProfile(type = DlnaProfileType.VIDEO),
DirectPlayProfile(type = DlnaProfileType.AUDIO),
Expand Down Expand Up @@ -145,7 +170,7 @@ class DeviceProfileBuilder {
return videoCodecs to audioCodecs
}

private fun getTranscodingProfiles(): List<TranscodingProfile> = ArrayList<TranscodingProfile>().apply {
private fun getExoPlayerTranscodingProfiles(): List<TranscodingProfile> = ArrayList<TranscodingProfile>().apply {
add(
TranscodingProfile(
type = DlnaProfileType.VIDEO,
Expand Down Expand Up @@ -207,6 +232,46 @@ class DeviceProfileBuilder {
)
}

private fun getMPVTranscodingProfiles(): List<TranscodingProfile> = ArrayList<TranscodingProfile>().apply {
add(
TranscodingProfile(
type = DlnaProfileType.AUDIO,
context = EncodingContext.STREAMING,

// TODO: remove redundant defaults after API/SDK is fixed
estimateContentLength = false,
enableMpegtsM2TsMode = false,
transcodeSeekInfo = TranscodeSeekInfo.AUTO,
copyTimestamps = false,
enableSubtitlesInManifest = false,
minSegments = 0,
segmentLength = 0,
breakOnNonKeyFrames = false,
)
)
add(
TranscodingProfile(
type = DlnaProfileType.VIDEO,
container = "ts",
videoCodec = "h264,h265,hevc,mpeg4,mpeg2video",
audioCodec = "mp1,mp2,mp3,aac,ac3,eac3,dts,mlp,truehd,opus,flac,vorbis",
context = EncodingContext.STREAMING,
protocol = "hls",
maxAudioChannels = "6",

// TODO: remove redundant defaults after API/SDK is fixed
estimateContentLength = false,
enableMpegtsM2TsMode = false,
transcodeSeekInfo = TranscodeSeekInfo.AUTO,
copyTimestamps = false,
enableSubtitlesInManifest = false,
minSegments = 0,
segmentLength = 0,
breakOnNonKeyFrames = false,
)
)
}

private fun getSubtitleProfiles(embedded: Array<String>, external: Array<String>): List<SubtitleProfile> = ArrayList<SubtitleProfile>().apply {
for (format in embedded) {
add(SubtitleProfile(format = format, method = SubtitleDeliveryMethod.EMBED))
Expand Down Expand Up @@ -304,5 +369,11 @@ class DeviceProfileBuilder {
private val EXTERNAL_PLAYER_SUBTITLES = arrayOf(
"ssa", "ass", "srt", "subrip", "idx", "sub", "vtt", "webvtt", "ttml", "pgs", "pgssub", "smi", "smil"
)
// https://github.com/mpv-player/mpv/blob/6857600c47f069aeb68232a745bc8f81d45c9967/player/external_files.c#L35
private val MPV_PLAYER_SUBTITLES = arrayOf(
"idx", "sub", "srt", "rt", "ssa", "ass", "mks",/* "vtt", */"sup", "scc", "smi", "lrc", "pgs",
// https://ffmpeg.org/general.html#Subtitle-Formats
"aqt", "jss", "txt", "mpsub", "pjs", "sami", "stl"
)
}
}
Loading

0 comments on commit 28e173b

Please sign in to comment.