You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于服务器时钟的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:45:32