kodi_free_time_thread.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import threading
  2. import time
  3. from datetime import datetime, time as dtime
  4. from utils.logger_config import logger
  5. from application.kodi_thread import _kodi_thread
  6. from application.kodi_alive_thread import start_kodi_alive_check, stop_kodi_alive_check
  7. class KodiFreeTimeThreadSingleton:
  8. """Kodi空闲时间播放线程单例类,每5分钟循环播放视频ID 0"""
  9. _instance = None
  10. _lock = threading.Lock()
  11. def __new__(cls):
  12. if cls._instance is None:
  13. with cls._lock:
  14. if cls._instance is None:
  15. cls._instance = super(KodiFreeTimeThreadSingleton, cls).__new__(cls)
  16. return cls._instance
  17. def __init__(self):
  18. # 避免重复初始化
  19. if hasattr(self, '_initialized'):
  20. return
  21. self._initialized = True
  22. self.thread = None
  23. self.is_running = False
  24. self._should_stop = False
  25. # 配置参数
  26. self.video_id = 0
  27. self.interval_seconds = 5 * 60 # 5分钟 = 300秒
  28. # 初始化状态:根据当前时间决定初始状态
  29. # 这个标志位是最终决定是否播放的开关
  30. # 它会受两个因素影响:1. 时间段变化自动触发 2. 用户手动控制
  31. self.is_playing_enabled = self._is_in_time_range()
  32. # 记录上一次的时间段状态,用于检测时间边缘变化
  33. self.last_in_range = self._is_in_time_range()
  34. # 启动工作线程
  35. self._start_worker_thread()
  36. def _is_in_time_range(self) -> bool:
  37. """检查当前时间是否在允许播放的时间段内 (07:30 - 18:00)"""
  38. now = datetime.now().time()
  39. start_time = dtime(7, 30)
  40. end_time = dtime(18, 0)
  41. return start_time <= now <= end_time
  42. def manual_start(self):
  43. """手动开启播放(临时启动)"""
  44. self.is_playing_enabled = True
  45. logger.info("Kodi闲时播放已手动开启")
  46. # 确保线程运行
  47. self._start_worker_thread()
  48. def manual_stop(self):
  49. """手动停止播放(临时停止)"""
  50. self.is_playing_enabled = False
  51. logger.info("Kodi闲时播放已手动关闭")
  52. def _start_worker_thread(self):
  53. """启动工作线程"""
  54. if self.thread is None or not self.thread.is_alive():
  55. self.is_running = True
  56. self._should_stop = False
  57. self.thread = threading.Thread(target=self._worker_loop, daemon=True)
  58. self.thread.start()
  59. logger.info("Kodi空闲时间播放线程已启动")
  60. def _worker_loop(self):
  61. """工作线程主循环"""
  62. logger.info("Kodi空闲时间播放线程开始运行,每5分钟播放视频ID 0(循环模式)")
  63. try:
  64. # 确保Kodi线程的manager已初始化
  65. _kodi_thread._initialize_manager()
  66. while self.is_running and not self._should_stop:
  67. try:
  68. # 1. 自动时间控制逻辑:检测时间段边缘变化
  69. current_in_range = self._is_in_time_range()
  70. if current_in_range != self.last_in_range:
  71. if current_in_range:
  72. # 刚刚进入时间段,自动开启
  73. logger.info("进入闲时播放时间段(07:30),自动开启播放开关")
  74. self.is_playing_enabled = True
  75. else:
  76. # 刚刚离开时间段,自动关闭
  77. logger.info("离开闲时播放时间段(18:00),自动关闭播放开关")
  78. self.is_playing_enabled = False
  79. self.last_in_range = current_in_range
  80. # 2. 执行播放逻辑(由 is_playing_enabled 决定,无论是由时间自动设置的还是手动设置的)
  81. if self.is_playing_enabled:
  82. # 调用播放方法,循环播放视频ID 0
  83. logger.info(f"触发空闲时间播放:视频ID={self.video_id},循环模式=True")
  84. success = _kodi_thread._play_sync_by_video_id(self.video_id, loop=True)
  85. if success:
  86. logger.info(f"空闲时间播放启动成功:视频ID={self.video_id}")
  87. else:
  88. logger.warning(f"空闲时间播放启动失败:视频ID={self.video_id}")
  89. else:
  90. logger.info("当前播放开关为关闭状态(手动停止或不在时间段内),跳过本次播放")
  91. # 等待5分钟后再次触发
  92. wait_time = self.interval_seconds
  93. # 如果当前未启用播放,缩短检查间隔以便更快响应时间变化或手动开启
  94. if not self.is_playing_enabled:
  95. wait_time = 60
  96. logger.info(f"等待 {wait_time} 秒后再次触发/检查")
  97. # 分段等待,以便能够响应停止信号
  98. waited = 0
  99. while waited < wait_time and not self._should_stop:
  100. # 检查是否有手动状态变更,如果有且需要立即响应(例如从未播放变播放),这里其实是等待下一轮循环
  101. # 但由于wait_time不长,或者用户操作会触发manual_start,下一轮自然会执行
  102. sleep_interval = min(1.0, wait_time - waited)
  103. time.sleep(sleep_interval)
  104. waited += sleep_interval
  105. if self._should_stop:
  106. logger.info("收到停止信号,退出循环")
  107. break
  108. except Exception as e:
  109. logger.error(f"工作线程循环异常: {e}")
  110. time.sleep(1)
  111. except Exception as e:
  112. logger.error(f"工作线程异常: {e}")
  113. finally:
  114. self.is_running = False
  115. logger.info("Kodi空闲时间播放线程结束")
  116. def stop(self):
  117. """停止线程"""
  118. self._should_stop = True
  119. self.is_running = False
  120. logger.info("停止Kodi空闲时间播放线程")
  121. # 全局单例实例
  122. _kodi_free_time_thread = KodiFreeTimeThreadSingleton()
  123. def start_kodi_free_time_play() -> bool:
  124. """
  125. 手动开启Kodi空闲时间播放(临时启动)
  126. 即使不在时间段内,也会强制开始播放
  127. Returns:
  128. bool: 操作是否成功
  129. """
  130. # 开启闲时播放的同时,关闭心跳检测
  131. _kodi_free_time_thread.manual_start()
  132. start_kodi_alive_check()
  133. return True
  134. def stop_kodi_free_time_play() -> bool:
  135. """
  136. 手动停止Kodi空闲时间播放(临时停止)
  137. 即使在时间段内,也会强制停止播放
  138. Returns:
  139. bool: 操作是否成功
  140. """
  141. # 停止闲时播放的同时,重新开启心跳检测
  142. _kodi_free_time_thread.manual_stop()
  143. stop_kodi_alive_check()
  144. return True
  145. def is_free_time_play_running() -> bool:
  146. """
  147. 检查空闲时间播放功能当前是否处于启用状态
  148. Returns:
  149. bool: 当前是否启用
  150. """
  151. return _kodi_free_time_thread.is_playing_enabled
  152. def get_free_time_play_status() -> dict:
  153. """
  154. 获取空闲时间播放的状态
  155. Returns:
  156. dict: 状态信息
  157. """
  158. return {
  159. "is_running": _kodi_free_time_thread.is_playing_enabled,
  160. "enabled": _kodi_free_time_thread.is_playing_enabled,
  161. "is_thread_alive": _kodi_free_time_thread.is_running,
  162. "video_id": _kodi_free_time_thread.video_id
  163. }