home/private_dot_config/private_mpv/scripts/osc/main.lua

966 lines
23 KiB
Lua

-- mpv oscf modern
-- by maoiscat
-- github.com/maoiscat/
package.path = package.path .. ";".. os.getenv("HOME") .. "/.config/mpv/lib/oscf/?.lua"
require("expansion")
local assdraw = require 'mp.assdraw'
-- user options
opts = {
scale = 1, -- osc render scale
fixedHeight = false, -- true to allow osc scale with window
hideTimeout = 1, -- seconds untile osc hides, negative means never
fadeDuration = 0.5, -- seconds during fade out, negative means never
}
-- logo and message works out of box
addToIdleLayout('logo')
-- define styles
local styles = {
background = {
color = {'0', '0', '0', '0'},
alpha = {255, 255, 0, 0},
border = 140,
blur = 140,
},
tooltip = {
color = {'FFFFFF', 'FFFFFF', '0', '0'},
border = 0.5,
blur = 1,
fontsize = 18,
wrap = 2,
},
button1 = {
color1 = {'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF'},
color2 = {'999999', '999999', '999999', '999999'},
fontsize = 36,
border = 0,
blur = 0,
font = 'material-design-iconic-font',
wrap = 2,
},
button2 = {
color1 = {'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF'},
color2 = {'999999', '999999', '999999', '999999'},
border = 0,
blur = 0,
fontsize = 24,
font = 'material-design-iconic-font',
wrap = 2,
},
seekbarFg = {
color1 = {'E39C42', 'E39C42', '0', '0'},
color2 = {'999999', '999999', '0', '0'},
border = 0.5,
blur = 1,
},
seekbarBg = {
color = {'eeeeee', 'eeeeee', '0', '0'},
border = 0,
blur = 0,
},
volumeSlider = {
color = {'ffffff', '0', '0', '0'},
border = 0,
blur = 0,
},
time = {
color1 = {'ffffff', 'ffffff', '0', '0'},
color2 = {'eeeeee', 'eeeeee', '0', '0'},
border = 0,
blur = 0,
fontsize = 17,
},
title = {
color = {'ffffff', '0', '0', '0'},
border = 0.5,
blur = 1,
fontsize = 48,
wrap = 2,
},
winControl = {
color1 = {'ffffff', 'ffffff', '0', '0'},
color2 = {'eeeeee', 'eeeeee', '0', '0'},
border = 0.5,
blur = 1,
font = 'mpv-osd-symbols',
fontsize = 20,
},
}
-- enviroment updater
-- this element updates shared vairables, sets active areas and starts event generators
local env
env = newElement('env')
env.layer = 1000
env.visible = false
env.updateTime = function()
dispatchEvent('time')
end
env.init = function(self)
self.slowTimer = mp.add_periodic_timer(0.25, self.updateTime) --use a slower timer to update playtime
-- event generators
mp.observe_property('track-list/count', 'native',
function(name, val)
if val==0 then return end
player.tracks = getTrackList()
player.playlist = getPlaylist()
player.chapters = getChapterList()
player.playlistPos = getPlaylistPos()
player.duration = mp.get_property_number('duration')
showOsc()
dispatchEvent('file-loaded')
end)
mp.observe_property('pause', 'bool',
function(name, val)
player.paused = val
dispatchEvent('pause')
end)
mp.observe_property('fullscreen', 'bool',
function(name, val)
player.fullscreen = val
dispatchEvent('fullscreen')
end)
mp.observe_property('window-maximized', 'bool',
function(name, val)
player.maximized = val
dispatchEvent('window-maximized')
end)
mp.observe_property('current-tracks/audio/id', 'number',
function(name, val)
if val then player.audioTrack = val
else player.audioTrack = 0
end
dispatchEvent('audio-changed')
end)
mp.observe_property('current-tracks/sub/id', 'number',
function(name, val)
if val then player.subTrack = val
else player.subTrack = 0
end
dispatchEvent('sub-changed')
end)
mp.observe_property('mute', 'bool',
function(name, val)
player.muted = val
dispatchEvent('mute')
end)
mp.observe_property('volume', 'number',
function(name, val)
player.volume = val
dispatchEvent('volume')
end)
end
env.tick = function(self)
player.percentPos = mp.get_property_number('percent-pos')
player.timePos = mp.get_property_number('time-pos')
player.timeRem = mp.get_property_number('time-remaining')
return ''
end
env.responder['resize'] = function(self)
player.geo.refX = player.geo.width / 2
player.geo.refY = player.geo.height - 40
setPlayActiveArea('bg1', 0, player.geo.height - 120, player.geo.width, player.geo.height)
if player.fullscreen then
setPlayActiveArea('wc1', player.geo.width - 200, 0, player.geo.width, 48)
else
setPlayActiveArea('wc1', -1, -1, -1, -1)
end
return false
end
env.responder['pause'] = function(self)
if player.idle then return end
if player.paused then
setVisibility('always')
else
setVisibility('normal')
end
end
env.responder['idle'] = function(self)
if player.idle then
setVisibility('always')
else
setVisibility('normal')
end
return false
end
env:init()
addToPlayLayout('env')
-- background
local ne
ne = newElement('background', 'box')
ne.geo.h = 1
ne.geo.an = 8
ne.layer = 5
-- DO NOT directly assign a shared style tabe!!
ne.style = clone(styles.background)
ne.responder['resize'] = function(self)
self.geo.x = player.geo.refX
self.geo.y = player.geo.height
self.geo.w = player.geo.width
self.setPos(self)
self.render(self)
return false
end
ne:init()
addToPlayLayout('background')
-- a shared tooltip
ne = newElement('tip', 'tooltip')
ne.layer = 20
ne.style = clone(styles.tooltip)
ne:init()
addToPlayLayout('tip')
local tooltip = ne
-- playpause button
ne = newElement('btnPlay', 'button')
ne.layer = 10
ne.style = clone(styles.button1)
ne.geo.w = 45
ne.geo.h = 45
ne.geo.an = 5
ne.responder['resize'] = function(self)
self.geo.x = player.geo.refX
self.geo.y = player.geo.refY
self:setPos()
self:setHitBox()
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('cycle', 'pause')
return true
end
return false
end
ne.responder['pause'] = function(self)
if player.paused then
self.text = '\xEF\x8E\xAA'
else
self.text = '\xEF\x8E\xA7'
end
self:render()
return false
end
ne:init()
addToPlayLayout('btnPlay')
-- skip back button
ne = newElement('btnBack', 'button')
ne.layer = 10
ne.style = clone(styles.button2)
ne.geo.w = 30
ne.geo.h = 24
ne.geo.an = 5
ne.text = '\xEF\x8E\xA0'
ne.responder['resize'] = function(self)
self.geo.x = player.geo.refX - 60
self.geo.y = player.geo.refY
self:setPos()
self:setHitBox()
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('seek', -5, 'relative', 'keyframes')
return true
end
return false
end
ne:init()
addToPlayLayout('btnBack')
-- skip forward button
ne = newElement('btnForward', 'btnBack')
ne.text = '\xEF\x8E\x9F'
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('seek', 5, 'relative', 'keyframes')
return true
end
return false
end
ne.responder['resize'] = function(self)
self.geo.x = player.geo.refX + 60
self.geo.y = player.geo.refY
self:setPos()
self:setHitBox()
return false
end
ne:init()
addToPlayLayout('btnForward')
-- play previous file button
ne = newElement('btnPrev', 'button')
ne.layer = 10
ne.style = clone(styles.button2)
ne.geo.w = 30
ne.geo.h = 24
ne.geo.an = 5
ne.text = '\xEF\x8E\xB5'
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('playlist-prev', 'weak')
return true
end
return false
end
ne.responder['resize'] = function(self)
self.geo.x = player.geo.refX - 120
self.geo.y = player.geo.refY
self:setPos()
self:setHitBox()
return false
end
ne.responder['file-loaded'] = function(self)
if player.playlistPos <= 1 and player.loopPlaylist == 'no' then
self:disable()
else
self:enable()
end
return false
end
ne:init()
addToPlayLayout('btnPrev')
-- play next file button
ne = newElement('btnNext', 'btnPrev')
ne.text = '\xEF\x8E\xB4'
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('playlist-next', 'weak')
return true
end
return false
end
ne.responder['resize'] = function(self)
self.geo.x = player.geo.refX + 120
self.geo.y = player.geo.refY
self:setPos()
self:setHitBox()
return false
end
ne.responder['file-loaded'] = function(self)
if player.playlistPos >= #player.playlist
and player.loopPlaylist == 'no' then
self:disable()
else
self:enable()
end
return false
end
ne:init()
addToPlayLayout('btnNext')
-- cycle audio button
ne = newElement('cycleAudio', 'button')
ne.layer = 10
ne.style = clone(styles.button2)
ne.geo.w = 30
ne.geo.h = 24
ne.geo.an = 5
ne.text = '\xEF\x8E\xB7'
ne.tipText = ''
ne.responder['resize'] = function(self)
self.geo.x = 37
self.geo.y = player.geo.refY
self.visible = player.geo.width >= 540
self:setPos()
self:setHitBox()
return false
end
ne.responder['mouse_move'] = function(self, pos)
if self.enabled and self:isInside(pos) then
tooltip:show(self.tipText, {self.geo.x, self.geo.y+30}, self)
return true
else
tooltip:hide(self)
return false
end
end
ne.responder['file-loaded'] = function(self)
if #player.tracks.audio > 0 then
self:enable()
else
self:disable()
end
end
ne.responder['audio-changed'] = function(self)
if player.tracks then
local lang
if player.audioTrack == 0 then
lang = 'OFF'
else
lang = player.tracks.audio[player.audioTrack].lang
end
if not lang then lang = 'unknown' end
self.tipText = string.format('[%s/%s][%s]',
player.audioTrack, #player.tracks.audio, lang)
tooltip:update(self.tipText, self)
end
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
cycleTrack('audio')
return true
end
return false
end
ne.responder['mbtn_right_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
cycleTrack('audio', 'prev')
return true
end
return false
end
ne:init()
addToPlayLayout('cycleAudio')
-- cycle sub button
ne = newElement('cycleSub', 'cycleAudio')
ne.text = '\xEF\x8F\x93'
ne.responder['resize'] = function(self)
self.geo.x = 87
self.geo.y = player.geo.refY
self.visible = player.geo.width >= 600
self:setPos()
self:setHitBox()
return false
end
ne.responder['file-loaded'] = function(self)
if #player.tracks.sub > 0 then
self:enable()
else
self:disable()
end
end
ne.responder['audio-changed'] = nil
ne.responder['sub-changed'] = function(self)
if player.tracks then
local title
if player.subTrack == 0 then
title = 'OFF'
else
title = player.tracks.sub[player.subTrack].title
end
if not title then title = 'unknown' end
self.tipText = string.format('[%s/%s][%s]',
player.subTrack, #player.tracks.sub, title)
tooltip:update(self.tipText, self)
end
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
cycleTrack('sub')
return true
end
return false
end
ne.responder['mbtn_right_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
cycleTrack('sub', 'prev')
return true
end
return false
end
ne:init()
addToPlayLayout('cycleSub')
-- toggle mute
ne = newElement('togMute', 'button')
ne.layer = 10
ne.style = clone(styles.button2)
ne.geo.x = 137
ne.geo.w = 30
ne.geo.h = 24
ne.geo.an = 5
ne.responder['resize'] = function(self)
self.geo.y = player.geo.refY
self.visible = player.geo.width >= 700
self:setPos()
self:setHitBox()
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('cycle', 'mute')
return true
end
return false
end
ne.responder['mute'] = function(self)
if player.muted then
self.text = '\xEF\x8E\xBB'
else
self.text = '\xEF\x8E\xBC'
end
self:render()
return false
end
ne:init()
addToPlayLayout('togMute', 'button')
-- volume slider
-- background
ne = newElement('volumeSliderBg', 'box')
ne.layer = 9
ne.style = clone(styles.volumeSlider)
ne.geo.r = 0
ne.geo.h = 1
ne.geo.an = 4
ne.responder['resize'] = function(self)
self.visible = player.geo.width > 740
self.geo.x = 156
self.geo.y = player.geo.refY
self.geo.w = 80
self:init()
end
ne:init()
addToPlayLayout('volumeSliderBg')
-- seekbar
ne = newElement('volumeSlider', 'slider')
ne.layer = 10
ne.style = clone(styles.volumeSlider)
ne.geo.an = 4
ne.geo.h = 14
ne.barHeight = 2
ne.barRadius = 0
ne.nobRadius = 4
ne.allowDrag = false
ne.lastSeek = nil
ne.responder['resize'] = function(self)
self.visible = player.geo.width > 740
self.geo.an = 4
self.geo.x = 152
self.geo.y = player.geo.refY
self.geo.w = 88
self:setParam() -- setParam may change geo settings
self:setPos()
self:render()
end
ne.responder['volume'] = function(self)
local val = player.volume
if val then
if val > 140 then val = 140
elseif val < 0 then val = 0 end
self.value = val/1.4
self.xValue = val/140 * self.xLength
self:render()
end
return false
end
ne.responder['idle'] = ne.responder['volume']
ne.responder['mouse_move'] = function(self, pos)
if not self.enabled then return false end
local vol = self:getValueAt(pos)
if self.allowDrag then
if vol then
mp.commandv('set', 'volume', vol*1.4)
env.updateTime()
end
end
if self:isInside(pos) then
local tipText
if vol then
tipText = string.format('%d', vol*1.4)
else
tipText = 'N/A'
end
tooltip:show(tipText, {pos[1], self.geo.y}, self)
return true
else
tooltip:hide(self)
return false
end
end
ne.responder['mbtn_left_down'] = function(self, pos)
if not self.enabled then return false end
if self:isInside(pos) then
self.allowDrag = true
local vol = self:getValueAt(pos)
if vol then
mp.commandv('set', 'volume', vol*1.4)
return true
end
end
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
self.allowDrag = false
self.lastSeek = nil
end
ne:init()
addToPlayLayout('volumeSlider')
-- toggle info
ne = newElement('togInfo', 'button')
ne.layer = 10
ne.style = clone(styles.button2)
ne.geo.w = 30
ne.geo.h = 24
ne.geo.an = 5
ne.text = '\xEF\x87\xB7'
ne.responder['resize'] = function(self)
self.geo.x = player.geo.width - 87
self.geo.y = player.geo.refY
self.visible = player.geo.width >= 640
self:setPos()
self:setHitBox()
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('script-binding', 'stats/display-stats-toggle')
return true
end
return false
end
ne:init()
addToPlayLayout('togInfo')
-- toggle fullscreen
ne = newElement('togFs', 'togInfo')
ne.text = '\xEF\x85\xAD'
ne.responder['resize'] = function(self)
self.geo.x = player.geo.width - 37
self.geo.y = player.geo.refY
self.visible = player.geo.width >= 600
if (player.fullscreen) then
self.text = '\xEF\x85\xAC'
else
self.text = '\xEF\x85\xAD'
end
self:render()
self:setPos()
self:setHitBox()
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.enabled and self:isInside(pos) then
mp.commandv('cycle', 'fullscreen')
return true
end
return false
end
ne:init()
addToPlayLayout('togFs')
-- seekbar background
ne = newElement('seekbarBg', 'box')
ne.layer = 9
ne.style = clone(styles.seekbarBg)
ne.geo.r = 0
ne.geo.h = 2
ne.geo.an = 5
ne.responder['resize'] = function(self)
self.geo.x = player.geo.refX
self.geo.y = player.geo.refY - 56
self.geo.w = player.geo.width - 50
self:init()
end
ne:init()
addToPlayLayout('seekbarBg')
-- seekbar
ne = newElement('seekbar', 'slider')
ne.layer = 10
ne.style = clone(styles.seekbarFg)
ne.geo.an = 5
ne.geo.h = 20
ne.barHeight = 2
ne.barRadius = 0
ne.nobRadius = 8
ne.allowDrag = false
ne.lastSeek = nil
ne.responder['resize'] = function(self)
self.geo.an = 5
self.geo.x = player.geo.refX
self.geo.y = player.geo.refY - 56
self.geo.w = player.geo.width - 34
self:setParam() -- setParam may change geo settings
self:setPos()
self:render()
end
ne.responder['time'] = function(self)
local val = player.percentPos
if val and not self.enabled then
self:enable()
elseif not val and self.enabled then
tooltip:hide(self)
self:disable()
end
if val then
self.value = val
self.xValue = val/100 * self.xLength
self:render()
end
return false
end
ne.responder['mouse_move'] = function(self, pos)
if not self.enabled then return false end
if self.allowDrag then
local seekTo = self:getValueAt(pos)
if seekTo then
mp.commandv('seek', seekTo, 'absolute-percent')
env.updateTime()
end
end
if self:isInside(pos) then
local tipText
if player.duration then
local seconds = self:getValueAt(pos)/100 * player.duration
if #player.chapters > 0 then
local ch = #player.chapters
for i, v in ipairs(player.chapters) do
if seconds < v.time then
ch = i - 1
break
end
end
if ch == 0 then
tipText = string.format('[0/%d][unknown]\\N%s',
#player.chapters, mp.format_time(seconds))
else
local title = player.chapters[ch].title
if not title then title = 'unknown' end
tipText = string.format('[%d/%d][%s]\\N%s',
ch, #player.chapters, title,
mp.format_time(seconds))
end
else
tipText = mp.format_time(seconds)
end
else
tipText = '--:--:--'
end
tooltip:show(tipText, {pos[1], self.geo.y}, self)
return true
else
tooltip:hide(self)
return false
end
end
ne.responder['mbtn_left_down'] = function(self, pos)
if not self.enabled then return false end
if self:isInside(pos) then
self.allowDrag = true
local seekTo = self:getValueAt(pos)
if seekTo then
mp.commandv('seek', seekTo, 'absolute-percent')
env.updateTime()
return true
end
end
return false
end
ne.responder['mbtn_left_up'] = function(self, pos)
self.allowDrag = false
self.lastSeek = nil
end
ne.responder['file-loaded'] = function(self)
-- update chapter markers
env.updateTime()
self.markers = {}
if player.duration then
for i, v in ipairs(player.chapters) do
self.markers[i] = (v.time*100 / player.duration)
end
self:render()
end
return false
end
ne:init()
addToPlayLayout('seekbar')
-- time display
ne = newElement('time1', 'button')
ne.layer = 10
ne.style = clone(styles.time)
ne.geo.w = 64
ne.geo.h = 20
ne.geo.an = 7
ne.enabled = true
ne.responder['resize'] = function(self)
self.geo.x = 25
self.geo.y = player.geo.refY - 44
self:setPos()
end
ne.responder['time'] = function(self)
if player.timePos then
self.pack[4] = mp.format_time(player.timePos)
else
self.pack[4] = '--:--:--'
end
end
ne:init()
addToPlayLayout('time1')
-- time duration
ne = newElement('time2', 'time1')
ne.geo.an = 9
ne.isDuration = true
ne.responder['resize'] = function(self)
self.geo.x = player.geo.width - 25
self.geo.y = player.geo.refY - 44
self:setPos()
self:setHitBox()
end
ne.responder['time'] = function(self)
if self.isDuration then
val = player.duration
else
val = -player.timeRem
end
if val then
self.pack[4] = mp.format_time(val)
else
self.pack[4] = '--:--:--'
end
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self:isInside(pos) then
self.isDuration = not self.isDuration
return true
end
return false
end
ne:init()
addToPlayLayout('time2')
-- title
ne = newElement('title')
ne.layer = 10
ne.style = clone(styles.title)
ne.geo.x = 20
ne.geo.an = 1
ne.visible = false
ne.title = ''
ne.render = function(self)
local maxchars = player.geo.width / 23
local text = self.title
-- 估计1个中文字符约等于1.5个英文字符
local charcount = (text:len() + select(2, text:gsub('[^\128-\193]', ''))*2) / 3
if not (maxchars == nil) and (charcount > maxchars) then
local limit = math.max(0, maxchars - 3)
if (charcount > limit) then
while (charcount > limit) do
text = text:gsub('.[\128-\191]*$', '')
charcount = (text:len() + select(2, text:gsub('[^\128-\193]', ''))*2) / 3
end
text = text .. '...'
end
end
self.pack[4] = text
end
ne.tick = function(self)
if not self.visible then return '' end
if self.trans >= 0.9 then
self.visible = false
end
return table.concat(self.pack)
end
ne.responder['resize'] = function(self)
self.geo.y = player.geo.refY - 92
self:setPos()
self:render()
self.visible = self.visible and (player.geo.height >= 320)
end
ne.responder['pause'] = function(self)
self.visible = (self.visible or player.paused) and (player.geo.height >= 320)
end
ne.responder['file-loaded'] = function(self)
local title = mp.command_native({'expand-text', '${media-title}'})
title = title:gsub('\\n', ' '):gsub('\\$', ''):gsub('{','\\{')
self.title = title
self:render()
self.visible = true
end
ne.responder['idle'] = function(self)
self.visible = not player.idle
return false
end
ne:init()
addToPlayLayout('title')
-- window controllers
ne = newElement('winClose', 'button')
ne.layer = 10
ne.style = clone(styles.winControl)
ne.geo.y = 16
ne.geo.w = 40
ne.geo.h = 32
ne.geo.an = 5
ne.text = '\238\132\149'
ne.responder['resize'] = function(self)
self.geo.x = player.geo.width - 20
self:init()
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.visible and self:isInside(pos) then
mp.commandv('quit')
return true
end
return false
end
ne.responder['fullscreen'] = function(self)
self.visible = player.fullscreen
end
ne:init()
addToPlayLayout('winClose')
ne = newElement('winMax', 'winClose')
ne.text = ''
ne.responder['resize'] = function(self)
self.geo.x = player.geo.width - 60
if player.maximized or player.fullscreen then
self.text = '\238\132\148'
else
self.text = '\238\132\147'
end
self:init()
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.visible and self:isInside(pos) then
if player.fullscreen then
mp.commandv('cycle', 'fullscreen')
else
mp.commandv('cycle', 'window-maximized')
end
return true
end
return false
end
ne:init()
addToPlayLayout('winMax')
ne = newElement('winMin', 'winClose')
ne.text = '\238\132\146'
ne.responder['resize'] = function(self)
self.geo.x = player.geo.width - 100
self:init()
end
ne.responder['mbtn_left_up'] = function(self, pos)
if self.visible and self:isInside(pos) then
mp.commandv('cycle', 'window-minimized')
return true
end
return false
end
ne:init()
addToPlayLayout('winMin')