reverted the changes from metroid to torchlight
This commit is contained in:
@@ -2,28 +2,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
import io
|
||||||
import math
|
import math
|
||||||
import time
|
|
||||||
from .FFmpegAudioPlayer import FFmpegAudioPlayerFactory
|
from .FFmpegAudioPlayer import FFmpegAudioPlayerFactory
|
||||||
|
|
||||||
def _progress_delta(old_position, new_position, elapsed = None, jitter = 0.25):
|
|
||||||
try:
|
|
||||||
delta = abs(float(new_position) - float(old_position))
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return 0.0
|
|
||||||
if not math.isfinite(delta):
|
|
||||||
return 0.0
|
|
||||||
if elapsed is not None:
|
|
||||||
try:
|
|
||||||
elapsed = float(elapsed)
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return 0.0
|
|
||||||
if not math.isfinite(elapsed) or elapsed < 0.0:
|
|
||||||
return 0.0
|
|
||||||
delta = min(delta, elapsed + jitter)
|
|
||||||
return delta
|
|
||||||
|
|
||||||
|
|
||||||
class AudioPlayerFactory():
|
class AudioPlayerFactory():
|
||||||
AUDIOPLAYER_FFMPEG = 1
|
AUDIOPLAYER_FFMPEG = 1
|
||||||
|
|
||||||
@@ -40,12 +22,9 @@ class AudioPlayerFactory():
|
|||||||
def NewPlayer(self, _type):
|
def NewPlayer(self, _type):
|
||||||
if _type == self.AUDIOPLAYER_FFMPEG:
|
if _type == self.AUDIOPLAYER_FFMPEG:
|
||||||
return self.FFmpegAudioPlayerFactory.NewPlayer()
|
return self.FFmpegAudioPlayerFactory.NewPlayer()
|
||||||
raise ValueError("Unsupported audio player type: {0}".format(_type))
|
|
||||||
|
|
||||||
|
|
||||||
class AntiSpam():
|
class AntiSpam():
|
||||||
GLOBAL_PLAY_TIME_IMMUNITY_LEVEL = 10
|
|
||||||
|
|
||||||
def __init__(self, master):
|
def __init__(self, master):
|
||||||
self.Logger = logging.getLogger(__class__.__name__)
|
self.Logger = logging.getLogger(__class__.__name__)
|
||||||
self.Master = master
|
self.Master = master
|
||||||
@@ -53,9 +32,7 @@ class AntiSpam():
|
|||||||
|
|
||||||
self.LastClips = dict()
|
self.LastClips = dict()
|
||||||
self.DisabledTime = None
|
self.DisabledTime = None
|
||||||
|
self.SaidHint = False
|
||||||
def _counts_toward_global_play_time(self, level):
|
|
||||||
return level < self.GLOBAL_PLAY_TIME_IMMUNITY_LEVEL
|
|
||||||
|
|
||||||
def CheckAntiSpam(self, player):
|
def CheckAntiSpam(self, player):
|
||||||
if self.DisabledTime and self.DisabledTime > self.Torchlight().Master.Loop.time() and \
|
if self.DisabledTime and self.DisabledTime > self.Torchlight().Master.Loop.time() and \
|
||||||
@@ -67,40 +44,36 @@ class AntiSpam():
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def SpamCheck(self):
|
def SpamCheck(self, Delta):
|
||||||
Now = self.Torchlight().Master.Loop.time()
|
Now = self.Torchlight().Master.Loop.time()
|
||||||
Duration = 0.0
|
Duration = 0.0
|
||||||
config = self.Torchlight().Config["AntiSpam"]
|
|
||||||
|
|
||||||
for Key, Clip in list(self.LastClips.items()):
|
for Key, Clip in list(self.LastClips.items()):
|
||||||
if not Clip["timestamp"]:
|
if not Clip["timestamp"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if Clip["timestamp"] + Clip["duration"] + config["MaxUsageSpan"] < Now:
|
if Clip["timestamp"] + Clip["duration"] + self.Torchlight().Config["AntiSpam"]["MaxUsageSpan"] < Now:
|
||||||
if not Clip["active"]:
|
if not Clip["active"]:
|
||||||
del self.LastClips[Key]
|
del self.LastClips[Key]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
Duration += Clip["duration"]
|
Duration += Clip["duration"]
|
||||||
|
|
||||||
if Duration > config["MaxUsageTime"]:
|
if Duration > self.Torchlight().Config["AntiSpam"]["MaxUsageTime"]:
|
||||||
self.DisabledTime = self.Torchlight().Master.Loop.time() + config["PunishDelay"]
|
self.DisabledTime = self.Torchlight().Master.Loop.time() + self.Torchlight().Config["AntiSpam"]["PunishDelay"]
|
||||||
self.Torchlight().SayChat("Blocked voice commands for the next {0} seconds. Used {1} seconds within {2} seconds.".format(
|
self.Torchlight().SayChat("Blocked voice commands for the next {0} seconds. Used {1} seconds within {2} seconds.".format(
|
||||||
config["PunishDelay"], config["MaxUsageTime"], config["MaxUsageSpan"]))
|
self.Torchlight().Config["AntiSpam"]["PunishDelay"], self.Torchlight().Config["AntiSpam"]["MaxUsageTime"], self.Torchlight().Config["AntiSpam"]["MaxUsageSpan"]))
|
||||||
|
|
||||||
# Make a copy of the list since AudioClip.Stop() will change the list
|
# Make a copy of the list since AudioClip.Stop() will change the list
|
||||||
for AudioClip in self.Master.AudioClips[:]:
|
for AudioClip in self.Master.AudioClips[:]:
|
||||||
if AudioClip.Level < config["ImmunityLevel"]:
|
if AudioClip.Level < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]:
|
||||||
AudioClip.Stop()
|
AudioClip.Stop()
|
||||||
|
|
||||||
self.LastClips.clear()
|
self.LastClips.clear()
|
||||||
|
|
||||||
def OnPlay(self, clip):
|
def OnPlay(self, clip):
|
||||||
if not self._counts_toward_global_play_time(clip.Level):
|
|
||||||
return
|
|
||||||
|
|
||||||
Now = self.Torchlight().Master.Loop.time()
|
Now = self.Torchlight().Master.Loop.time()
|
||||||
self.LastClips[clip] = dict({"timestamp": Now, "duration": 0.0, "dominant": False, "active": True, "last_update_realtime": time.time()})
|
self.LastClips[hash(clip)] = dict({"timestamp": Now, "duration": 0.0, "dominant": False, "active": True})
|
||||||
|
|
||||||
HasDominant = False
|
HasDominant = False
|
||||||
for Key, Clip in self.LastClips.items():
|
for Key, Clip in self.LastClips.items():
|
||||||
@@ -108,50 +81,31 @@ class AntiSpam():
|
|||||||
HasDominant = True
|
HasDominant = True
|
||||||
break
|
break
|
||||||
|
|
||||||
self.LastClips[clip]["dominant"] = not HasDominant
|
self.LastClips[hash(clip)]["dominant"] = not HasDominant
|
||||||
|
|
||||||
def OnStop(self, clip):
|
def OnStop(self, clip):
|
||||||
if not self._counts_toward_global_play_time(clip.Level):
|
if hash(clip) not in self.LastClips:
|
||||||
return
|
return
|
||||||
|
|
||||||
if clip not in self.LastClips:
|
self.LastClips[hash(clip)]["active"] = False
|
||||||
return
|
|
||||||
|
|
||||||
self.LastClips[clip]["active"] = False
|
if self.LastClips[hash(clip)]["dominant"]:
|
||||||
|
|
||||||
if self.LastClips[clip]["dominant"]:
|
|
||||||
for Key, Clip in self.LastClips.items():
|
for Key, Clip in self.LastClips.items():
|
||||||
if Clip["active"]:
|
if Clip["active"]:
|
||||||
Clip["dominant"] = True
|
Clip["dominant"] = True
|
||||||
break
|
break
|
||||||
|
|
||||||
self.LastClips[clip]["dominant"] = False
|
self.LastClips[hash(clip)]["dominant"] = False
|
||||||
|
|
||||||
def OnUpdate(self, clip, old_position, new_position):
|
def OnUpdate(self, clip, old_position, new_position):
|
||||||
if not self._counts_toward_global_play_time(clip.Level):
|
Delta = new_position - old_position
|
||||||
return
|
Clip = self.LastClips[hash(clip)]
|
||||||
|
|
||||||
if clip not in self.LastClips:
|
if not Clip["dominant"]:
|
||||||
if self.Logger.isEnabledFor(logging.DEBUG):
|
|
||||||
self.Logger.debug(
|
|
||||||
"OnUpdate called for unknown clip key %r; %d known keys",
|
|
||||||
clip,
|
|
||||||
len(self.LastClips),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
Clip = self.LastClips[clip]
|
|
||||||
now = time.time()
|
|
||||||
elapsed = None
|
|
||||||
if Clip.get("last_update_realtime") is not None:
|
|
||||||
elapsed = now - Clip["last_update_realtime"]
|
|
||||||
Clip["last_update_realtime"] = now
|
|
||||||
Delta = _progress_delta(old_position, new_position, elapsed = elapsed)
|
|
||||||
|
|
||||||
if Delta <= 0.0 or not Clip["dominant"]:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
Clip["duration"] += Delta
|
Clip["duration"] += Delta
|
||||||
self.SpamCheck()
|
self.SpamCheck(Delta)
|
||||||
|
|
||||||
|
|
||||||
class Advertiser():
|
class Advertiser():
|
||||||
@@ -161,33 +115,37 @@ class Advertiser():
|
|||||||
self.Torchlight = self.Master.Torchlight
|
self.Torchlight = self.Master.Torchlight
|
||||||
|
|
||||||
self.LastClips = dict()
|
self.LastClips = dict()
|
||||||
|
self.AdStop = 0
|
||||||
|
self.NextAdStop = 0
|
||||||
|
|
||||||
def Think(self, clip):
|
def Think(self, Delta):
|
||||||
Now = self.Torchlight().Master.Loop.time()
|
Now = self.Torchlight().Master.Loop.time()
|
||||||
config = self.Torchlight().Config["Advertiser"]
|
Duration = 0.0
|
||||||
|
|
||||||
for Key, Clip in list(self.LastClips.items()):
|
for Key, Clip in list(self.LastClips.items()):
|
||||||
if not Clip["timestamp"]:
|
if not Clip["timestamp"]:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if Clip["timestamp"] + Clip["duration"] + config["MaxSpan"] < Now:
|
if Clip["timestamp"] + Clip["duration"] + self.Torchlight().Config["Advertiser"]["MaxSpan"] < Now:
|
||||||
if not Clip["active"]:
|
if not Clip["active"]:
|
||||||
del self.LastClips[Key]
|
del self.LastClips[Key]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
Duration += Clip["duration"]
|
||||||
|
|
||||||
Clip = self.LastClips.get(clip)
|
self.NextAdStop -= Delta
|
||||||
if not Clip:
|
CeilDur = math.ceil(Duration)
|
||||||
return
|
if CeilDur > self.AdStop and self.NextAdStop <= 0 and CeilDur % self.Torchlight().Config["Advertiser"]["AdStop"] == 0:
|
||||||
|
|
||||||
if not clip.StopHinted and not Clip.get("hinted") and Clip["duration"] >= config["AdStop"]:
|
|
||||||
self.Torchlight().SayChat("Hint: Type \x07FF0000!stop(ze) !pls(mg)\x01 to stop all currently playing sounds.")
|
self.Torchlight().SayChat("Hint: Type \x07FF0000!stop(ze) !pls(mg)\x01 to stop all currently playing sounds.")
|
||||||
Clip["hinted"] = True
|
self.AdStop = CeilDur
|
||||||
clip.StopHinted = True
|
self.NextAdStop = 0
|
||||||
|
elif CeilDur < self.AdStop:
|
||||||
|
self.AdStop = 0
|
||||||
|
self.NextAdStop = self.Torchlight().Config["Advertiser"]["AdStop"] / 2
|
||||||
|
|
||||||
def OnPlay(self, clip):
|
def OnPlay(self, clip):
|
||||||
Now = self.Torchlight().Master.Loop.time()
|
Now = self.Torchlight().Master.Loop.time()
|
||||||
self.LastClips[clip] = dict({"timestamp": Now, "duration": 0.0, "dominant": False, "active": True, "hinted": False, "last_update_realtime": time.time()})
|
self.LastClips[hash(clip)] = dict({"timestamp": Now, "duration": 0.0, "dominant": False, "active": True})
|
||||||
|
|
||||||
HasDominant = False
|
HasDominant = False
|
||||||
for Key, Clip in self.LastClips.items():
|
for Key, Clip in self.LastClips.items():
|
||||||
@@ -195,38 +153,31 @@ class Advertiser():
|
|||||||
HasDominant = True
|
HasDominant = True
|
||||||
break
|
break
|
||||||
|
|
||||||
self.LastClips[clip]["dominant"] = not HasDominant
|
self.LastClips[hash(clip)]["dominant"] = not HasDominant
|
||||||
|
|
||||||
def OnStop(self, clip):
|
def OnStop(self, clip):
|
||||||
if clip not in self.LastClips:
|
if hash(clip) not in self.LastClips:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.LastClips[clip]["active"] = False
|
self.LastClips[hash(clip)]["active"] = False
|
||||||
|
|
||||||
if self.LastClips[clip]["dominant"]:
|
if self.LastClips[hash(clip)]["dominant"]:
|
||||||
for Key, Clip in self.LastClips.items():
|
for Key, Clip in self.LastClips.items():
|
||||||
if Clip["active"]:
|
if Clip["active"]:
|
||||||
Clip["dominant"] = True
|
Clip["dominant"] = True
|
||||||
break
|
break
|
||||||
|
|
||||||
self.LastClips[clip]["dominant"] = False
|
self.LastClips[hash(clip)]["dominant"] = False
|
||||||
|
|
||||||
def OnUpdate(self, clip, old_position, new_position):
|
def OnUpdate(self, clip, old_position, new_position):
|
||||||
if clip not in self.LastClips:
|
Delta = new_position - old_position
|
||||||
return
|
Clip = self.LastClips[hash(clip)]
|
||||||
Clip = self.LastClips[clip]
|
|
||||||
now = time.time()
|
|
||||||
elapsed = None
|
|
||||||
if Clip.get("last_update_realtime") is not None:
|
|
||||||
elapsed = now - Clip["last_update_realtime"]
|
|
||||||
Clip["last_update_realtime"] = now
|
|
||||||
Delta = _progress_delta(old_position, new_position, elapsed = elapsed)
|
|
||||||
|
|
||||||
if Delta <= 0.0 or not Clip["dominant"]:
|
if not Clip["dominant"]:
|
||||||
return
|
return
|
||||||
|
|
||||||
Clip["duration"] += Delta
|
Clip["duration"] += Delta
|
||||||
self.Think(clip)
|
self.Think(Delta)
|
||||||
|
|
||||||
|
|
||||||
class AudioManager():
|
class AudioManager():
|
||||||
@@ -246,23 +197,21 @@ class AudioManager():
|
|||||||
if player.Access:
|
if player.Access:
|
||||||
Level = player.Access["level"]
|
Level = player.Access["level"]
|
||||||
|
|
||||||
config = self.Torchlight().Config["AudioLimits"]
|
if str(Level) in self.Torchlight().Config["AudioLimits"]:
|
||||||
if str(Level) in config:
|
if self.Torchlight().Config["AudioLimits"][str(Level)]["Uses"] >= 0 and \
|
||||||
level_config = config[str(Level)]
|
player.Storage["Audio"]["Uses"] >= self.Torchlight().Config["AudioLimits"][str(Level)]["Uses"]:
|
||||||
if level_config["Uses"] >= 0 and \
|
|
||||||
player.Storage["Audio"]["Uses"] >= level_config["Uses"]:
|
|
||||||
|
|
||||||
self.Torchlight().SayPrivate(player, "You have used up all of your free uses! ({0} uses)".format(
|
self.Torchlight().SayPrivate(player, "You have used up all of your free uses! ({0} uses)".format(
|
||||||
level_config["Uses"]))
|
self.Torchlight().Config["AudioLimits"][str(Level)]["Uses"]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if player.Storage["Audio"]["TimeUsed"] >= level_config["TotalTime"]:
|
if player.Storage["Audio"]["TimeUsed"] >= self.Torchlight().Config["AudioLimits"][str(Level)]["TotalTime"]:
|
||||||
self.Torchlight().SayPrivate(player, "You have used up all of your free time! ({0} seconds)".format(
|
self.Torchlight().SayPrivate(player, "You have used up all of your free time! ({0} seconds)".format(
|
||||||
level_config["TotalTime"]))
|
self.Torchlight().Config["AudioLimits"][str(Level)]["TotalTime"]))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
TimeElapsed = self.Torchlight().Master.Loop.time() - player.Storage["Audio"]["LastUse"]
|
TimeElapsed = self.Torchlight().Master.Loop.time() - player.Storage["Audio"]["LastUse"]
|
||||||
UseDelay = player.Storage["Audio"]["LastUseLength"] * level_config["DelayFactor"]
|
UseDelay = player.Storage["Audio"]["LastUseLength"] * self.Torchlight().Config["AudioLimits"][str(Level)]["DelayFactor"]
|
||||||
|
|
||||||
if TimeElapsed < UseDelay:
|
if TimeElapsed < UseDelay:
|
||||||
self.Torchlight().SayPrivate(player, "You are currently on cooldown! ({0} seconds left)".format(
|
self.Torchlight().SayPrivate(player, "You are currently on cooldown! ({0} seconds left)".format(
|
||||||
@@ -272,34 +221,31 @@ class AudioManager():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def Stop(self, player, extra):
|
def Stop(self, player, extra):
|
||||||
Result = {"active": 0, "matched": 0, "stopped": 0, "pending": 0}
|
Level = 0
|
||||||
stopper_level = player.Access["level"] if player.Access else 0
|
if player.Access:
|
||||||
|
Level = player.Access["level"]
|
||||||
|
|
||||||
for AudioClip in self.AudioClips[:]:
|
for AudioClip in self.AudioClips[:]:
|
||||||
Result["active"] += 1
|
if extra and not extra.lower() in AudioClip.Player.Name.lower():
|
||||||
target_level = AudioClip.Player.Access["level"] if AudioClip.Player.Access else 0
|
|
||||||
same_player = False
|
|
||||||
if getattr(player, "UniqueID", None) is not None and getattr(AudioClip.Player, "UniqueID", None) is not None:
|
|
||||||
same_player = player.UniqueID == AudioClip.Player.UniqueID
|
|
||||||
elif getattr(player, "UserID", None) is not None and getattr(AudioClip.Player, "UserID", None) is not None:
|
|
||||||
same_player = player.UserID == AudioClip.Player.UserID
|
|
||||||
else:
|
|
||||||
same_player = player == AudioClip.Player
|
|
||||||
|
|
||||||
if not same_player and target_level > stopper_level:
|
|
||||||
Result["pending"] += 1
|
|
||||||
continue
|
continue
|
||||||
Result["matched"] += 1
|
|
||||||
|
|
||||||
|
if not Level or (Level < AudioClip.Level and Level < self.Torchlight().Config["AntiSpam"]["StopLevel"]):
|
||||||
|
AudioClip.Stops.add(player.UserID)
|
||||||
|
|
||||||
|
if len(AudioClip.Stops) >= 3:
|
||||||
AudioClip.Stop()
|
AudioClip.Stop()
|
||||||
Result["stopped"] += 1
|
|
||||||
self.Torchlight().SayPrivate(AudioClip.Player, "Your audio clip was stopped.")
|
self.Torchlight().SayPrivate(AudioClip.Player, "Your audio clip was stopped.")
|
||||||
if not same_player:
|
if player != AudioClip.Player:
|
||||||
|
self.Torchlight().SayPrivate(player, "Stopped \"{0}\"({1}) audio clip.".format(AudioClip.Player.Name, AudioClip.Player.UserID))
|
||||||
|
else:
|
||||||
|
self.Torchlight().SayPrivate(player, "This audio clip needs {0} more !stop's.".format(3 - len(AudioClip.Stops)))
|
||||||
|
else:
|
||||||
|
AudioClip.Stop()
|
||||||
|
self.Torchlight().SayPrivate(AudioClip.Player, "Your audio clip was stopped.")
|
||||||
|
if player != AudioClip.Player:
|
||||||
self.Torchlight().SayPrivate(player, "Stopped \"{0}\"({1}) audio clip.".format(AudioClip.Player.Name, AudioClip.Player.UserID))
|
self.Torchlight().SayPrivate(player, "Stopped \"{0}\"({1}) audio clip.".format(AudioClip.Player.Name, AudioClip.Player.UserID))
|
||||||
|
|
||||||
return Result
|
def AudioClip(self, player, uri, _type = AudioPlayerFactory.AUDIOPLAYER_FFMPEG):
|
||||||
|
|
||||||
def CreateAudioClip(self, player, uri, _type = AudioPlayerFactory.AUDIOPLAYER_FFMPEG):
|
|
||||||
Level = 0
|
Level = 0
|
||||||
if player.Access:
|
if player.Access:
|
||||||
Level = player.Access["level"]
|
Level = player.Access["level"]
|
||||||
@@ -314,19 +260,10 @@ class AudioManager():
|
|||||||
if not self.CheckLimits(player):
|
if not self.CheckLimits(player):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
|
||||||
Clip = AudioClip(self, player, uri, _type)
|
Clip = AudioClip(self, player, uri, _type)
|
||||||
except ValueError as ex:
|
|
||||||
self.Logger.warning("Rejected unsupported audio player type %r: %s", _type, ex)
|
|
||||||
self.Torchlight().SayPrivate(player, "Unsupported audio player type.")
|
|
||||||
return None
|
|
||||||
self.AudioClips.append(Clip)
|
self.AudioClips.append(Clip)
|
||||||
|
|
||||||
anti_spam_track_ceiling = min(
|
if not player.Access or player.Access["level"] < self.Torchlight().Config["AntiSpam"]["ImmunityLevel"]:
|
||||||
self.Torchlight().Config["AntiSpam"]["ImmunityLevel"],
|
|
||||||
self.AntiSpam.GLOBAL_PLAY_TIME_IMMUNITY_LEVEL,
|
|
||||||
)
|
|
||||||
if not player.Access or player.Access["level"] < anti_spam_track_ceiling:
|
|
||||||
Clip.AudioPlayer.AddCallback("Play", lambda *args: self.AntiSpam.OnPlay(Clip, *args))
|
Clip.AudioPlayer.AddCallback("Play", lambda *args: self.AntiSpam.OnPlay(Clip, *args))
|
||||||
Clip.AudioPlayer.AddCallback("Stop", lambda *args: self.AntiSpam.OnStop(Clip, *args))
|
Clip.AudioPlayer.AddCallback("Stop", lambda *args: self.AntiSpam.OnStop(Clip, *args))
|
||||||
Clip.AudioPlayer.AddCallback("Update", lambda *args: self.AntiSpam.OnUpdate(Clip, *args))
|
Clip.AudioPlayer.AddCallback("Update", lambda *args: self.AntiSpam.OnUpdate(Clip, *args))
|
||||||
@@ -337,9 +274,6 @@ class AudioManager():
|
|||||||
|
|
||||||
return Clip
|
return Clip
|
||||||
|
|
||||||
def AudioClip(self, player, uri, _type = AudioPlayerFactory.AUDIOPLAYER_FFMPEG):
|
|
||||||
return self.CreateAudioClip(player, uri, _type)
|
|
||||||
|
|
||||||
def OnDisconnect(self, player):
|
def OnDisconnect(self, player):
|
||||||
for AudioClip in self.AudioClips[:]:
|
for AudioClip in self.AudioClips[:]:
|
||||||
if AudioClip.Player == player:
|
if AudioClip.Player == player:
|
||||||
@@ -355,9 +289,6 @@ class AudioClip():
|
|||||||
self.Type = _type
|
self.Type = _type
|
||||||
self.URI = uri
|
self.URI = uri
|
||||||
self.LastPosition = None
|
self.LastPosition = None
|
||||||
self.LastUpdateRealtime = None
|
|
||||||
self.StartedAt = None
|
|
||||||
self.StopHinted = False
|
|
||||||
self.Stops = set()
|
self.Stops = set()
|
||||||
|
|
||||||
self.Level = 0
|
self.Level = 0
|
||||||
@@ -385,28 +316,15 @@ class AudioClip():
|
|||||||
self.Player.Storage["Audio"]["Uses"] += 1
|
self.Player.Storage["Audio"]["Uses"] += 1
|
||||||
self.Player.Storage["Audio"]["LastUse"] = self.Torchlight().Master.Loop.time()
|
self.Player.Storage["Audio"]["LastUse"] = self.Torchlight().Master.Loop.time()
|
||||||
self.Player.Storage["Audio"]["LastUseLength"] = 0.0
|
self.Player.Storage["Audio"]["LastUseLength"] = 0.0
|
||||||
self.StartedAt = self.Player.Storage["Audio"]["LastUse"]
|
|
||||||
self.LastUpdateRealtime = time.time()
|
|
||||||
self.StopHinted = False
|
|
||||||
|
|
||||||
def OnStop(self):
|
def OnStop(self):
|
||||||
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + self.URI)
|
self.Logger.debug(sys._getframe().f_code.co_name + ' ' + self.URI)
|
||||||
if self in self.Master.AudioClips:
|
|
||||||
self.Master.AudioClips.remove(self)
|
self.Master.AudioClips.remove(self)
|
||||||
|
|
||||||
played = None
|
if self.AudioPlayer.Playing:
|
||||||
if self.AudioPlayer and self.AudioPlayer.StartedPlaying is not None:
|
Delta = self.AudioPlayer.Position - self.LastPosition
|
||||||
elapsed = time.time() - self.AudioPlayer.StartedPlaying
|
self.Player.Storage["Audio"]["TimeUsed"] += Delta
|
||||||
if self.AudioPlayer.Seconds:
|
self.Player.Storage["Audio"]["LastUseLength"] += Delta
|
||||||
elapsed = min(elapsed, self.AudioPlayer.Seconds)
|
|
||||||
played = max(0.0, elapsed)
|
|
||||||
|
|
||||||
if played is not None:
|
|
||||||
last_length = self.Player.Storage["Audio"]["LastUseLength"]
|
|
||||||
if played > last_length:
|
|
||||||
delta = played - last_length
|
|
||||||
self.Player.Storage["Audio"]["TimeUsed"] += delta
|
|
||||||
self.Player.Storage["Audio"]["LastUseLength"] += delta
|
|
||||||
|
|
||||||
if str(self.Level) in self.Torchlight().Config["AudioLimits"]:
|
if str(self.Level) in self.Torchlight().Config["AudioLimits"]:
|
||||||
if self.Player.Storage:
|
if self.Player.Storage:
|
||||||
@@ -420,18 +338,9 @@ class AudioClip():
|
|||||||
del self.AudioPlayer
|
del self.AudioPlayer
|
||||||
|
|
||||||
def OnUpdate(self, old_position, new_position):
|
def OnUpdate(self, old_position, new_position):
|
||||||
now = time.time()
|
Delta = new_position - old_position
|
||||||
elapsed = None
|
|
||||||
if self.LastUpdateRealtime is not None:
|
|
||||||
elapsed = now - self.LastUpdateRealtime
|
|
||||||
self.LastUpdateRealtime = now
|
|
||||||
|
|
||||||
Delta = _progress_delta(old_position, new_position, elapsed = elapsed)
|
|
||||||
self.LastPosition = new_position
|
self.LastPosition = new_position
|
||||||
|
|
||||||
if Delta <= 0.0:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.Player.Storage["Audio"]["TimeUsed"] += Delta
|
self.Player.Storage["Audio"]["TimeUsed"] += Delta
|
||||||
self.Player.Storage["Audio"]["LastUseLength"] += Delta
|
self.Player.Storage["Audio"]["LastUseLength"] += Delta
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -43,7 +43,6 @@ class FFmpegAudioPlayer():
|
|||||||
self.Torchlight().Config["VoiceServer"]["Port"]
|
self.Torchlight().Config["VoiceServer"]["Port"]
|
||||||
)
|
)
|
||||||
self.SampleRate = float(self.Torchlight().Config["VoiceServer"]["SampleRate"])
|
self.SampleRate = float(self.Torchlight().Config["VoiceServer"]["SampleRate"])
|
||||||
self.Channels = int(self.Torchlight().Config["VoiceServer"].get("Channels", 2))
|
|
||||||
|
|
||||||
self.StartedPlaying = None
|
self.StartedPlaying = None
|
||||||
self.StoppedPlaying = None
|
self.StoppedPlaying = None
|
||||||
@@ -60,71 +59,38 @@ class FFmpegAudioPlayer():
|
|||||||
|
|
||||||
def PlayURI(self, uri, position, rubberband = None, dec_params = None, bitrate = None,
|
def PlayURI(self, uri, position, rubberband = None, dec_params = None, bitrate = None,
|
||||||
backwards = None, *args):
|
backwards = None, *args):
|
||||||
if self.Playing:
|
|
||||||
self.Stop()
|
|
||||||
|
|
||||||
proxy_url = ""
|
|
||||||
if uri.startswith("http://") or uri.startswith("https://"):
|
|
||||||
proxy_url = self.Torchlight().Config["Proxy"]
|
|
||||||
|
|
||||||
if position:
|
if position:
|
||||||
PosStr = str(datetime.timedelta(seconds=position))
|
PosStr = str(datetime.timedelta(seconds = position))
|
||||||
Command = ["/usr/bin/ffmpeg"]
|
#Command = ["/usr/bin/ffmpeg", "-ss", PosStr, "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args]
|
||||||
if proxy_url:
|
Command = ["/usr/bin/ffmpeg", "-ss", PosStr, "-i", uri, "-af", "highpass=f=20", "-acodec", "pcm_s16le", "-ac", "2", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args]
|
||||||
Command += ["-http_proxy", proxy_url]
|
|
||||||
Command += ["-ss", PosStr, "-i", uri, "-acodec", "pcm_s16le", "-ac", str(self.Channels), "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn"]
|
|
||||||
else:
|
else:
|
||||||
Command = ["/usr/bin/ffmpeg"]
|
#Command = ["/usr/bin/ffmpeg", "-i", uri, "-acodec", "pcm_s16le", "-ac", "1", "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn", *args]
|
||||||
if proxy_url:
|
Command = ["/usr/bin/ffmpeg", "-i", uri, "-af", "highpass=f=20", "-acodec", "pcm_s16le", "-ac", "2", "-ar", str(int(self.SampleRate)),"-f", "s16le", "-vn"]
|
||||||
Command += ["-http_proxy", proxy_url]
|
|
||||||
Command += ["-i", uri, "-acodec", "pcm_s16le", "-ac", str(self.Channels), "-ar", str(int(self.SampleRate)), "-f", "s16le", "-vn"]
|
|
||||||
|
|
||||||
if args:
|
|
||||||
Command += list(args)
|
|
||||||
|
|
||||||
# Reset per-play counters/state in case this instance is reused.
|
|
||||||
self.Seconds = 0.0
|
|
||||||
self.StartedPlaying = None
|
|
||||||
self.StoppedPlaying = None
|
|
||||||
self._byte_remainder = 0
|
|
||||||
self.Playing = True
|
self.Playing = True
|
||||||
|
|
||||||
if dec_params:
|
if dec_params:
|
||||||
Command += dec_params
|
Command += dec_params
|
||||||
|
|
||||||
filter_chain = ["highpass=f=20"]
|
if rubberband and backwards:
|
||||||
|
Command += ["-filter:a"]
|
||||||
|
rubberCommand = ""
|
||||||
|
for rubber in rubberband:
|
||||||
|
rubberCommand += rubber + ", "
|
||||||
|
rubberCommand = rubberCommand[:-2]
|
||||||
|
Command += [rubberCommand + "[reversed];[reversed]areverse"] #[reversed] is intermediate stream label so reverse knows what stream label to reverse
|
||||||
|
else:
|
||||||
if rubberband:
|
if rubberband:
|
||||||
# Treat rubberband as either a single pre-built filtergraph string
|
Command += ["-filter:a"]
|
||||||
# or an iterable of simple, comma-safe filters. Validate to avoid
|
rubberCommand = ""
|
||||||
# generating invalid ffmpeg filter syntax.
|
for rubber in rubberband:
|
||||||
rubberband_filters = []
|
rubberCommand += rubber + ", "
|
||||||
if isinstance(rubberband, str):
|
rubberCommand = rubberCommand[:-2]
|
||||||
rubberband_filters = [rubberband]
|
Command += [rubberCommand]
|
||||||
else:
|
|
||||||
try:
|
|
||||||
iterator = iter(rubberband)
|
|
||||||
except TypeError:
|
|
||||||
# Not iterable: coerce to string and treat as single element.
|
|
||||||
rubberband_filters = [str(rubberband)]
|
|
||||||
else:
|
|
||||||
for f in iterator:
|
|
||||||
if not isinstance(f, str):
|
|
||||||
f = str(f)
|
|
||||||
# Reject potentially complex/unsafe filter fragments that
|
|
||||||
# could break the filtergraph when joined with commas.
|
|
||||||
if any(ch in f for ch in [",", ";", "[", "]"]):
|
|
||||||
self.Master.Logger.error(
|
|
||||||
"Skipping unsafe rubberband filter fragment %r: "
|
|
||||||
"contains ',', ';', '[' or ']'", f
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
rubberband_filters.append(f)
|
|
||||||
filter_chain.extend(rubberband_filters)
|
|
||||||
if backwards:
|
if backwards:
|
||||||
filter_chain.append("areverse")
|
Command += ["-af"]
|
||||||
Command += ["-filter:a", ",".join(filter_chain)]
|
Command += ["areverse"]
|
||||||
if bitrate:
|
if bitrate:
|
||||||
Command += ["-b:a", f"{bitrate}k"]
|
Command += ["-ab ", str(bitrate), "k"]
|
||||||
self.Master.Logger.debug(f"command: {Command}")
|
self.Master.Logger.debug(f"command: {Command}")
|
||||||
Command += ["-"]
|
Command += ["-"]
|
||||||
#self.Master.Logger.debug(f"command: {Command}")
|
#self.Master.Logger.debug(f"command: {Command}")
|
||||||
@@ -157,7 +123,7 @@ class FFmpegAudioPlayer():
|
|||||||
self.Playing = False
|
self.Playing = False
|
||||||
|
|
||||||
self.Callback("Stop")
|
self.Callback("Stop")
|
||||||
self.Callbacks = []
|
del self.Callbacks
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -186,48 +152,31 @@ class FFmpegAudioPlayer():
|
|||||||
SecondsElapsed = self.Seconds
|
SecondsElapsed = self.Seconds
|
||||||
|
|
||||||
self.Callback("Update", LastSecondsElapsed, SecondsElapsed)
|
self.Callback("Update", LastSecondsElapsed, SecondsElapsed)
|
||||||
LastSecondsElapsed = SecondsElapsed
|
|
||||||
|
|
||||||
if SecondsElapsed >= self.Seconds:
|
if SecondsElapsed >= self.Seconds:
|
||||||
if not self.StoppedPlaying:
|
if not self.StoppedPlaying:
|
||||||
self.Master.Logger.warning("BUFFER UNDERRUN")
|
print("BUFFER UNDERRUN!")
|
||||||
await asyncio.sleep(0.05)
|
|
||||||
continue
|
|
||||||
self.Stop(False)
|
self.Stop(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
LastSecondsElapsed = SecondsElapsed
|
||||||
|
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
async def _read_stream(self, stream, writer):
|
async def _read_stream(self, stream, writer):
|
||||||
Started = False
|
Started = False
|
||||||
try:
|
|
||||||
while stream and self.Playing:
|
while stream and self.Playing:
|
||||||
Data = await stream.read(65536)
|
Data = await stream.read(65536)
|
||||||
|
|
||||||
if Data:
|
if Data:
|
||||||
try:
|
|
||||||
writer.write(Data)
|
writer.write(Data)
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
except (ConnectionError, BrokenPipeError, OSError) as ex:
|
|
||||||
self.Master.Logger.warning("Voice socket write failed: %s", ex)
|
|
||||||
break
|
|
||||||
|
|
||||||
Bytes = len(Data)
|
Bytes = len(Data)
|
||||||
frame_size = SAMPLEBYTES * self.Channels
|
Samples = Bytes / SAMPLEBYTES
|
||||||
|
Seconds = Samples / self.SampleRate
|
||||||
|
|
||||||
# Guard against invalid configuration and use integral frame counting
|
|
||||||
if frame_size <= 0 or self.SampleRate <= 0:
|
|
||||||
self.Master.Logger.error(
|
|
||||||
"Invalid audio configuration: Channels=%r, SampleRate=%r",
|
|
||||||
self.Channels,
|
|
||||||
self.SampleRate,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Accumulate remainder bytes to avoid systematic undercounting
|
|
||||||
total_bytes = getattr(self, "_byte_remainder", 0) + Bytes
|
|
||||||
Frames = total_bytes // frame_size
|
|
||||||
self._byte_remainder = total_bytes % frame_size
|
|
||||||
Seconds = Frames / self.SampleRate
|
|
||||||
self.Seconds += Seconds
|
self.Seconds += Seconds
|
||||||
|
|
||||||
if not Started:
|
if not Started:
|
||||||
@@ -238,13 +187,13 @@ class FFmpegAudioPlayer():
|
|||||||
else:
|
else:
|
||||||
self.Process = None
|
self.Process = None
|
||||||
break
|
break
|
||||||
finally:
|
|
||||||
self.StoppedPlaying = time.time()
|
self.StoppedPlaying = time.time()
|
||||||
|
|
||||||
async def _stream_subprocess(self, cmd):
|
async def _stream_subprocess(self, cmd):
|
||||||
if not self.Playing:
|
if not self.Playing:
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
_, self.Writer = await asyncio.open_connection(self.Host[0], self.Host[1])
|
_, self.Writer = await asyncio.open_connection(self.Host[0], self.Host[1])
|
||||||
|
|
||||||
Process = await asyncio.create_subprocess_exec(*cmd,
|
Process = await asyncio.create_subprocess_exec(*cmd,
|
||||||
@@ -254,15 +203,5 @@ class FFmpegAudioPlayer():
|
|||||||
await self._read_stream(Process.stdout, self.Writer)
|
await self._read_stream(Process.stdout, self.Writer)
|
||||||
await Process.wait()
|
await Process.wait()
|
||||||
|
|
||||||
if self.Playing:
|
if self.Seconds == 0.0:
|
||||||
# Keep the clip active until the updater reaches self.Seconds.
|
|
||||||
# This allows !stop to interrupt short clips that are already
|
|
||||||
# buffered to the voice socket but still audible in-game.
|
|
||||||
if self.StartedPlaying is None:
|
|
||||||
self.Stop(False)
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
except Exception:
|
|
||||||
self.Master.Logger.error(traceback.format_exc())
|
|
||||||
if self.Playing:
|
|
||||||
self.Stop()
|
self.Stop()
|
||||||
|
|||||||
Reference in New Issue
Block a user