基于服务器时钟的Android应用分时段视频播放功能实现咨询
基于服务器时钟的Android分时段视频播放实现方案
嘿,这个需求我刚好有过类似的落地经验,给你梳理一套完整的实现思路和代码示例,亲测好用:
核心思路
因为依赖服务器时间而非本地设备时间,核心关键点是:先同步服务器时间并维护时间偏移量,后续通过本地时间+偏移量快速计算服务器时间;然后基于这个时间做时段判断,同时处理应用前后台切换、定时同步等场景,确保时间准确性。
分步实现
1. 服务器时间同步工具类
先做一个工具类管理服务器时间的同步和获取,避免重复请求服务器:
object ServerTimeManager { private var serverTimeOffset: Long = 0 // 服务器时间 - 本地时间的偏移量 // 从服务器获取当前UTC毫秒级时间戳 suspend fun syncServerTime() { try { val response = ApiService.getServerTime() val serverTimestamp = response.timestamp val localTimestamp = System.currentTimeMillis() serverTimeOffset = serverTimestamp - localTimestamp } catch (e: Exception) { // 请求失败时保留上次偏移量,或提示用户检查网络 e.printStackTrace() } } // 获取当前服务器时间(毫秒级) fun getCurrentServerTime(): Long { return System.currentTimeMillis() + serverTimeOffset } }
2. 主页面的播放/倒计时逻辑
在视频播放页面,处理初始加载、时段判断、视频播放和倒计时展示:
class VideoPlayerActivity : AppCompatActivity() { private lateinit var videoView: VideoView private lateinit var countdownTv: TextView private var countdownTimer: CountDownTimer? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_video_player) videoView = findViewById(R.id.video_view) countdownTv = findViewById(R.id.countdown_tv) // 初始化时先同步服务器时间,再处理播放逻辑 lifecycleScope.launch { ServerTimeManager.syncServerTime() handleVideoPlayback() } // 每5分钟同步一次服务器时间,避免偏移过大 Timer().scheduleAtFixedRate(object : TimerTask() { override fun run() { lifecycleScope.launch { ServerTimeManager.syncServerTime() handleVideoPlayback() } } }, 0, 5 * 60 * 1000) } private fun handleVideoPlayback() { // 先取消之前的倒计时和播放任务 countdownTimer?.cancel() videoView.stopPlayback() countdownTv.visibility = View.GONE videoView.visibility = View.VISIBLE val serverTime = ServerTimeManager.getCurrentServerTime() // 转换为当天的时分秒(忽略年月日,只比较时段) val dayTimeCalendar = Calendar.getInstance().apply { timeInMillis = serverTime set(Calendar.YEAR, 2000) set(Calendar.MONTH, 0) set(Calendar.DAY_OF_MONTH, 1) } val dayTimeMillis = dayTimeCalendar.timeInMillis // 定义各个时段的时间范围(基于2000-01-01的时分秒) val slot1Start = getTimeMillis(8, 0, 0) val slot1End = getTimeMillis(8, 4, 59) val slot2Start = getTimeMillis(8, 5, 0) val slot2End = getTimeMillis(8, 9, 59) val slot3Start = getTimeMillis(8, 10, 0) val slot3End = getTimeMillis(8, 14, 59) when { dayTimeMillis in slot1Start..slot1End -> playVideo("video_url_1") dayTimeMillis in slot2Start..slot2End -> playVideo("video_url_2") dayTimeMillis in slot3Start..slot3End -> playVideo("video_url_3") else -> showCountdown(serverTime) } } // 辅助方法:获取指定时分秒对应的毫秒数(基于2000-01-01) private fun getTimeMillis(hour: Int, minute: Int, second: Int): Long { return Calendar.getInstance().apply { set(2000, 0, 1, hour, minute, second) set(Calendar.MILLISECOND, 0) }.timeInMillis } private fun playVideo(videoUrl: String) { videoView.setVideoURI(Uri.parse(videoUrl)) videoView.setOnPreparedListener { it.start() } // 视频播放完成后重新判断时段,避免刚好进入下一个时段 videoView.setOnCompletionListener { handleVideoPlayback() } } private fun showCountdown(currentServerTime: Long) { videoView.visibility = View.GONE countdownTv.visibility = View.VISIBLE val nextSlotStartTime = calculateNextSlotStartTime(currentServerTime) val remainingMillis = nextSlotStartTime - currentServerTime countdownTimer = object : CountDownTimer(remainingMillis, 1000) { override fun onTick(millisUntilFinished: Long) { val hours = (millisUntilFinished / (1000 * 60 * 60)).toInt() val minutes = ((millisUntilFinished % (1000 * 60 * 60)) / (1000 * 60)).toInt() val seconds = ((millisUntilFinished % (1000 * 60)) / 1000).toInt() countdownTv.text = String.format("距离下一次播放还有 %02d:%02d:%02d", hours, minutes, seconds) } override fun onFinish() { // 倒计时结束自动切换到对应视频 handleVideoPlayback() } }.start() } // 计算下一个播放时段的开始时间 private fun calculateNextSlotStartTime(currentServerTime: Long): Long { val calendar = Calendar.getInstance().apply { timeInMillis = currentServerTime } val hour = calendar.get(Calendar.HOUR_OF_DAY) val minute = calendar.get(Calendar.MINUTE) return when { // 当前时间在08:15之后,下一个时段是第二天的08:00 hour > 8 || (hour == 8 && minute >= 15) -> { calendar.apply { add(Calendar.DAY_OF_MONTH, 1) set(Calendar.HOUR_OF_DAY, 8) set(Calendar.MINUTE, 0) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) }.timeInMillis } // 当前时间在08:00之前,下一个时段是当天的08:00 else -> { calendar.apply { set(Calendar.HOUR_OF_DAY, 8) set(Calendar.MINUTE, 0) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) }.timeInMillis } } } override fun onResume() { super.onResume() // 应用从后台回到前台时,重新同步服务器时间并更新状态 lifecycleScope.launch { ServerTimeManager.syncServerTime() handleVideoPlayback() } } override fun onDestroy() { super.onDestroy() countdownTimer?.cancel() videoView.stopPlayback() } }
关键注意事项
- 时区处理:建议服务器返回UTC时间戳,客户端转换成本地时区时间,避免不同时区用户出现时段判断错误。
- 异常容错:服务器时间请求失败时保留上次偏移量,或弹出提示让用户检查网络;视频加载失败时也要重新触发时段判断逻辑。
- 后台同步频率:不要过于频繁请求服务器(5-10分钟一次即可),避免被服务器限流。
- Doze模式适配:如果需要后台持续同步时间,可能需要申请
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限,但一般用户打开应用时同步就足够满足需求。
内容的提问来源于stack exchange,提问作者Ayan
相关产品推荐
相关产品推荐

