// e_player_list.c #include "e_player_list.h" #include #include #include #include #include "e_logger.h" #include #include "e_conf.h" #include #define GET_SCR_WIDTH() (LV_USE_HOR_SIZE) #define GET_SCR_HEIGHT() (LV_USE_VER_SIZE) #define USEC_TO_SECONDS (1000 * 1000) #define TOLERANCE_MS 200 // 误差容忍度200ms #define retry_max 5 // 播放失败最大重试次数 #define USEC_TO_MS 1000 // 微秒转毫秒 #define MS_TO_USEC 1000 // 毫秒转微秒 #define DECODE_REDUNDANCY_MS 1500 // 解码性能差设备的时间冗余 (1 秒) static bool sync_play = true; // 是否开启同步播放 static int retry_count = 0; extern int g_screen_rotation; // ======================== 内部函数声明 ======================== static void *player_thread_func(void *arg); static void dis_player_hole(int display_idx); static int my_play_callback(void *user, int evt, void *info); static int play_video(VideoPlayer *video_player, const MediaPath *file_path); static void play_error(VideoPlayer *video_player, const int code); static void xos_player_set_volume(void *player, int volume); extern void fbdev_set_hole(int x, int y, int width, int height); extern void fbdev2_set_hole(int x, int y, int width, int height); // ======================== 播放器API实现 ======================== QUA_BOOL is_stop = QUA_FALSE; VideoPlayer *video_player_init(int display_idx) { VideoPlayer *player = calloc(1, sizeof(VideoPlayer)); if (!player) return NULL; player->display_idx = display_idx; snprintf(player->display, sizeof(player->display), "id:display%d", display_idx); player->media_paths = calloc(MAX_PLAYLIST_ITEMS, sizeof(MediaPath *)); if (!player->media_paths) { LOGE("播放列表初始化失败:内存分配失败"); free(player); return NULL; } pthread_mutex_init(&player->playlist_mutex, NULL); pthread_mutex_init(&player->play_mutex, NULL); pthread_cond_init(&player->play_cond, NULL); return player; } // 设置是否开启同步播放 void video_player_set_sync_play(bool sync) { sync_play = sync; } /** * 临时关闭播放器,不销毁video_player。 * 清空播放列表,并停止播放 */ void video_player_close(VideoPlayer *video_player) { if (!video_player) return; video_player->first_play = true; if (video_player->stop_requested) { // 当下载失败后。黑洞已打开,但是视频并没有开始播放,因此此处添加关闭黑洞 dis_player_hole(video_player->display_idx); return; } LOGI("开始关闭播放器"); // 停止播放线程 video_player->stop_requested = true; // 发送条件变量信号通知播放线程停止 pthread_mutex_lock(&video_player->play_mutex); pthread_cond_signal(&video_player->play_cond); pthread_mutex_unlock(&video_player->play_mutex); // 等待播放线程结束,设置超时避免死锁 if (video_player->thread_running) { LOGI("等待播放线程结束"); struct timespec timeout; clock_gettime(CLOCK_REALTIME, &timeout); timeout.tv_sec += 3; // 设置3秒超时 int join_result = pthread_timedjoin_np(video_player->player_thread, NULL, &timeout); if (join_result == ETIMEDOUT) { LOGE("播放线程结束超时,强制取消线程"); pthread_cancel(video_player->player_thread); } video_player->thread_running = false; LOGI("播放线程已结束"); } // 安全销毁播放器 if (video_player->player) { LOGI("安全销毁播放器"); // 先尝试正常停止 int stop_result = qua_mm_player_stop(video_player->player); if (stop_result != 0) { LOGE("播放器stop失败,ret=%d,尝试强制重置", stop_result); qua_mm_player_reset(video_player->player); } // 等待一小段时间确保清理完成 usleep(100 * 1000); // 100ms qua_mm_player_destroy(video_player->player); video_player->player = NULL; LOGI("播放器销毁完成"); } // 清空播放列表 video_player_clear_playlist(video_player); LOGI("关闭黑洞显示"); // 关闭黑洞 dis_player_hole(video_player->display_idx); LOGI("播放器关闭完成"); } /** * 销毁播放器 */ void video_player_destroy(VideoPlayer *video_player) { if (!video_player) return; // 先关闭播放器 if (!video_player->stop_requested) { video_player_close(video_player); } // 清理播放列表 pthread_mutex_lock(&video_player->playlist_mutex); for (int i = 0; i < video_player->media_count; i++) { if (video_player->media_paths[i]) { free(video_player->media_paths[i]); video_player->media_paths[i] = NULL; } } free(video_player->media_paths); // 释放二级指针数组 video_player->media_paths = NULL; video_player->media_count = 0; pthread_mutex_unlock(&video_player->playlist_mutex); // 确保播放线程已停止 video_player->stop_requested = true; if (video_player->thread_running) { pthread_join(video_player->player_thread, NULL); } // 确保播放器已清理 if (video_player->player) { LOGI("video_player_destroy中清理剩余播放器"); qua_mm_player_stop(video_player->player); qua_mm_player_destroy(video_player->player); video_player->player = NULL; } // 关闭黑洞 dis_player_hole(video_player->display_idx); pthread_mutex_destroy(&video_player->playlist_mutex); free(video_player); } // ======================== 线程安全的播放列表管理 ======================== void video_player_add_item(VideoPlayer *player, const MediaPath *local_path) { if (!player || !local_path) return; pthread_mutex_lock(&player->playlist_mutex); if (player->media_count < MAX_PLAYLIST_ITEMS) { player->media_paths[player->media_count++] = local_path; } pthread_mutex_unlock(&player->playlist_mutex); } void video_player_clear_playlist(VideoPlayer *player) { if (!player) return; pthread_mutex_lock(&player->playlist_mutex); for (int i = 0; i < player->media_count; i++) { if (player->media_paths[i]) { free(player->media_paths[i]); player->media_paths[i] = NULL; } } player->media_count = 0; player->current_index = 0; player->first_play = true; pthread_mutex_unlock(&player->playlist_mutex); } void video_player_set_double_buffer(VideoPlayer *video_player) { if (!video_player) return; video_player_clear_playlist(video_player); // video_player->media_count = 0; // video_player->current_index = 0; } void video_player_set_size(VideoPlayer *video_player, e_player_area area) { if (!video_player) return; if (area.width > GET_SCR_WIDTH()) { area.width = GET_SCR_WIDTH(); } if (area.height > GET_SCR_HEIGHT()) { area.height = GET_SCR_HEIGHT(); } video_player->area = area; video_player_set_hole(video_player); } // ======================== 播放控制 ======================== // 获取毫秒时间戳 long long get_current_time_ms() { struct timespec ts; // 获取单调时钟(自系统启动后流逝的时间,不受系统时间修改影响) if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { LOGE("clock_gettime failed"); return 0; } return (long long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; // 转换为毫秒 } int video_player_play(VideoPlayer *player) { if (!player) return -1; // 确保有可播放的内容 pthread_mutex_lock(&player->playlist_mutex); bool has_media = (player->media_count > 0); pthread_mutex_unlock(&player->playlist_mutex); if (!has_media) { LOGW("播放列表为空"); return -1; } if (player->thread_running) { LOGW("播放线程已在运行"); return -1; } player->stop_requested = false; player->first_play = true; // player->actual_play_ms = 0; // 重置播放耗时统计 if (pthread_create(&player->player_thread, NULL, player_thread_func, player) == 0) { player->thread_running = true; LOGI("播放线程已启动"); return 0; } else { LOGE("播放线程创建失败"); return -1; } } void video_player_next(VideoPlayer *player) { if (!player) return; pthread_mutex_lock(&player->playlist_mutex); if (player->media_count > 0) { player->current_index = (player->current_index + 1) % player->media_count; } pthread_mutex_unlock(&player->playlist_mutex); } void video_player_set_hole(VideoPlayer *video_player) { if (!video_player) return; // 直接使用视频区域,不做坐标转换 // 因为 QuaPlayer 库已经处理了旋转后的实际显示位置 int x = video_player->area.x; int y = video_player->area.y; int width = video_player->area.width; int height = video_player->area.height; if (video_player->display_idx == 0) { LOGD("设置视频挖洞区域。x=%d,y=%d,w=%d,h=%d", x, y, width, height); fbdev_set_hole(x, y, width, height); } else { LOGD("设置视频挖洞区域。x=%d,y=%d,w=%d,h=%d", x, y, width, height); fbdev2_set_hole(x, y, width, height); } } // ======================== 内部实现 ======================== static void *player_thread_func(void *arg) { VideoPlayer *player = (VideoPlayer *)arg; while (!player->stop_requested) { pthread_mutex_lock(&player->playlist_mutex); if (player->media_count == 0) { pthread_mutex_unlock(&player->playlist_mutex); LOGW("播放列表为空,停止播放"); break; } int index = player->current_index % player->media_count; MediaPath *media_path = player->media_paths[index]; // char file_path[MAX_URL_LEN]; // strncpy(file_path, player->media_paths[index], MAX_URL_LEN); pthread_mutex_unlock(&player->playlist_mutex); if (!media_path) { player->current_index = (player->current_index + 1) % player->media_count; continue; } // LOGI("开始播放第 %d 个文件: %s", index, file_path); is_stop = QUA_FALSE; int ret = play_video(player, media_path); if (player->first_play) { player->first_play = false; } // player->start_time_ms=0; // 重置播放开始时间 if (ret != 0) { retry_count++; if (retry_count > retry_max) { // if(player->media_count==1){ // LOGE("播放失败,超过最大重试次数%d:%s",retry_count,file_path); // break; // } LOGE("播放失败,超过最大重试次数%d:%s", retry_count, media_path->path); break; } // else{ LOGE("播放失败,等待1秒后重试%d:%s", retry_count, media_path->path); // sleep(2); // } sleep(1); // continue; } else { retry_count = 0; // LOGD("视频播放完成,理论时长:% lldms,实际耗时:% lldms(显示器 % d)", // player->video_duration_ms, player->actual_play_ms, player->display_idx); } // 播放完成后切换下一个(根据双屏冗余调整切换时机) // #if DISP_COUNT == 2 // // 双屏模式:确保所有设备都播放完成后再切换(等待冗余时间) // if (sync_play){ // long long wait_switch_ms = (player->video_duration_ms + DECODE_REDUNDANCY_MS) - player->actual_play_ms; // if (wait_switch_ms > 0) // { // LOGD("双屏模式等待冗余时间:% lldms(显示器 % d)", wait_switch_ms, player->display_idx); // usleep(wait_switch_ms * MS_TO_USEC); // } // } // #endif // 播放完成或失败后切换下一个 player->current_index = (player->current_index + 1) % player->media_count; // LOGI("开始切换下一个视频"); } player->thread_running = false; LOGW("播放线程退出(显示器 % d)", player->display_idx); return NULL; } // 计算显示区域,根据视频比例和屏幕比例,计算显示区域的大小和位置。视频大小必须为8的倍数 static void calculate_display_rect(int img_width, int img_height, lv_area_t *area, qua_rect_t *rect, bool auto_center) { int is_screen_landscape = GET_SCR_WIDTH() > GET_SCR_HEIGHT(); int is_image_landscape = img_width > img_height; int is_adaptive = (is_image_landscape != is_screen_landscape); // LV_LOG_USER("area [%d %d] area rect size [%d %d]", area->x1, area->y1, area->x2, area->y2); // LV_LOG_USER("img_width: %d, img_height:%d\n", img_width, img_height); rect->x = area->x1; rect->y = area->y1; rect->width = area->x2 - area->x1 + 1; rect->height = area->y2 - area->y1 + 1; if (auto_center) { if (true) { float screen_ratio = (float)GET_SCR_WIDTH() / GET_SCR_HEIGHT(); float image_ratio = (float)img_width / img_height; // LV_LOG_USER("screen_ratio =%f,image_ratio=%d", screen_ratio, image_ratio); if (screen_ratio > image_ratio) { rect->width = GET_SCR_HEIGHT() * img_width / img_height; rect->height = GET_SCR_HEIGHT(); rect->x = (GET_SCR_WIDTH() - rect->width) / 2; rect->y = 0; if (rect->x > 0 && rect->x % 2 > 0) rect->x += 1; } else { rect->width = GET_SCR_WIDTH(); rect->height = GET_SCR_WIDTH() * img_height / img_width; rect->x = 0; rect->y = (GET_SCR_HEIGHT() - rect->height) / 2; if (rect->y > 0 && rect->y % 2 > 0) rect->y += 1; // rect->height = (area->y2- area->y1)/ 4 *4; // LV_LOG_USER("#######"); } } } else { rect->x = area->x1 / 2 * 2; rect->y = area->y1 / 2 * 2; rect->width = area->x2 / 8 * 8; rect->height = area->y2 / 8 * 8; // LV_LOG_USER("@@@@@@@@@"); } rect->width = rect->width / 8 * 8; rect->height = rect->height / 8 * 8; LOGD("display rect [%d %d %d %d]", rect->x, rect->y, rect->width, rect->height); } static int play_video(VideoPlayer *video_player, const MediaPath *media_path) { void *player = NULL; QUA_S32 ret = 0; QUA_BOOL pos_cb = QUA_TRUE; if (!video_player->player) { if (video_player->display_idx == 0) { player = qua_mm_player_create("e_video_play"); } else { player = qua_mm_player_create("e_video_play1"); } if (player) { qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_DISPLAY_ID, (QUA_VOID_PTR)video_player->display); qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_ROTATE, (QUA_VOID_PTR)&g_screen_rotation); xos_player_set_volume(player, 0); qua_mm_player_set_loop(player, QUA_FALSE); video_player->player = player; } } else { player = video_player->player; // LOGI("reset player"); qua_mm_player_reset(player); } if (!player) { LOGE("创建播放器失败"); return -1; } ret = qua_mm_player_set_data_source(player, media_path->path); if (ret != 0) { LOGE("设置播放源失败 %s", media_path->path); goto fail; } qua_size_t size; qua_mm_player_get_parameter(player, KEY_PARAMETER_VIDEO_SIZE, (QUA_VOID_PTR)&size); // LOGI("获取到视频分辨率 %dx%d", size.width, size.height); if (size.width * size.height > 1920 * 1080) { LOGE("视频分辨率超过1920*1080"); goto fail; } e_player_area video_area = video_player->area; lv_area_t area = {0}; area.x1 = video_area.x; area.x2 = video_area.width; area.y1 = video_area.y; area.y2 = video_area.height; qua_rect_t rect = {0}; calculate_display_rect(size.width, size.height, &area, &rect, false); qua_rect_t chn_rect; chn_rect.x = 0; chn_rect.y = 0; chn_rect.width = GET_SCR_WIDTH(); chn_rect.height = GET_SCR_HEIGHT(); qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_DISPLAY_RECT, (QUA_VOID_PTR)&chn_rect); qua_mm_player_set_parameter(player, KEY_PARAMETER_VO_CHN_RECT, (QUA_VOID_PTR)&rect); ret = qua_mm_player_prepare(player); if (ret != 0) { LOGE("准备播放失败. ret=%d", ret); goto fail; } ret = qua_mm_player_set_callback(player, my_play_callback, video_player); if (ret != 0) { LOGE("设置回调失败. ret=%d", ret); goto fail; } // qua_mm_player_set_parameter(player, KEY_PARAMETER_SET_CURRENT_POSITION_CALLBACK, (QUA_VOID *)&pos_cb); // 获取视频时长并计算同步起始时间 long long video_duration_us = qua_mm_player_get_durationUs(player); // 转换为毫秒 video_player->video_duration_ms = video_duration_us / 1000; // 转换为毫秒 LOGD("获取视频时长:% lldms(显示器 % d)", video_player->video_duration_ms, video_player->display_idx); // 同步播放处理 // QUA_S32 qua_mm_player_seek(QUA_VOID* player, QUA_U64 timeUs); long long play_position_ms = 0; if (sync_play && video_player->media_count == 1) { // long long currentTime=get_current_time_ms(); // long long minuteMillis= video_player->video_duration_ms+1000; // if (video_player->first_play) // { // // 首次开机时 系统时间尚未校时,因此可能计算的位置不对 // long long viode_duration_ms = video_player->video_duration_ms + 600; // long long positionMs = get_current_time_ms() % viode_duration_ms; // 当前应该播放的位置(毫秒) // qua_mm_player_seek(player, positionMs * 1000); // 转换为微秒 // play_position_ms = positionMs; // 使播放器提前结束,防止下次播放时错过开始时间 // } // else // { // long long viode_duration_ms= video_player->video_duration_ms+2000; long long viode_duration_ms = video_player->video_duration_ms + 1000; // long long positionMs = get_current_time_ms() % viode_duration_ms;// 当前应该播放的位置(毫秒) long long t_remainder = get_current_time_ms() % viode_duration_ms; LOGI("距离视频开始:%lld", t_remainder); long long tolerance = 300; while (!is_stop && !video_player->stop_requested) { long long currentTime = get_current_time_ms(); long long remainder = currentTime % viode_duration_ms; if (remainder <= tolerance || (viode_duration_ms - remainder) <= tolerance) { break; // 开始播放 } usleep(50 * MS_TO_USEC); } //} } video_player_set_hole(video_player); ret = qua_mm_player_start(player); if (ret != 0) { LOGE("播放器启动失败,ret=% d(显示器 % d)", ret, video_player->display_idx); goto fail; } if (play_position_ms > 0) { video_player->last_play_time_ms = get_current_time_ms() - play_position_ms; } else { video_player->last_play_time_ms = get_current_time_ms(); // 记录播放开始时间 } // 计算结束时间 // long long end_time = get_current_time_ms() + video_player->video_duration_ms; long long end_time = video_player->last_play_time_ms + video_player->video_duration_ms; pthread_mutex_lock(&video_player->play_mutex); while (get_current_time_ms() < end_time && !video_player->stop_requested) { int status = pthread_cond_timedwait(&video_player->play_cond, &video_player->play_mutex, (const struct timespec *)&(struct timespec){.tv_sec = end_time / 1000, .tv_nsec = (end_time % 1000) * 1000000}); if (status == ETIMEDOUT) { break; } } pthread_mutex_unlock(&video_player->play_mutex); // 发送播放通知 // event_play_start(media_path); // usleep(video_duration_us); // 使用循环检查方式替代usleep,支持提前退出 // long long video_duration_ms = video_duration_us / 1000; // 转换为毫秒 // while (get_current_time_ms() - start_time < video_player->video_duration_ms&&!is_stop) { // if (video_player->stop_requested) { // LOGI("播放被提前终止(显示器 %d)", video_player->display_idx); // qua_mm_player_stop(player); // return 0; // } // usleep(20 * 1000); // 20ms检查一次 // } // 如果播放器中途被结束,则跳过 if (video_player->stop_requested) { return 0; } // LOGI("视频播放结束01"); // LOGI("~~~~~~~~~~~~~~~~视频播放结束~~~~~~~~~~~~~~~~"); ret = qua_mm_player_stop(player); if (ret != 0) { LOGE("停止播放失败 "); goto fail; } qua_mm_player_reset(player); return 0; fail: if (player) { qua_mm_player_destroy(player); player = NULL; video_player->player = NULL; } return -1; } static void xos_player_set_volume(void *player, int volume) { // LV_LOG_USER("player volume is %d\n", volume); qua_mm_player_set_volume(player, volume); } /** * 视频播放器消息回调 */ static int my_play_callback(void *user, int evt, void *info) { VideoPlayer *video_player = (VideoPlayer *)user; if (evt == PLAYER_EVT_PLAYBACK_COMPLETE) { LOGD("收到播放完成回调(显示器 % d)", video_player->display_idx); is_stop = QUA_TRUE; // pthread_mutex_lock(&video_player->is_stop_mutex); // video_player->is_stop = QUA_TRUE; // pthread_mutex_unlock(&video_player->is_stop_mutex); } else if (evt == PLAYER_EVT_CURRENT_POSITION) { // long pos = (long long)(*(QUA_U64 *)info) / USEC_TO_SECONDS; // LOGI("video callback event POSITION %d", pos); } else { LOGD("收到播放器事件:% d(显示器 % d)", evt, video_player->display_idx); } return 0; } static void play_error(VideoPlayer *video_player, const int code) { LOGE("播放器出现异常,错误码: % d(显示器 % d)", code, video_player->display_idx); } static void dis_player_hole(int display_idx) { // 视频播放时,设置挖洞区域。 if (display_idx == 0) { fbdev_set_hole(0, 0, 0, 0); } else { fbdev2_set_hole(0, 0, 0, 0); } } /* * 检查播放器是否正常 */ void video_check_play_state(VideoPlayer *player) { if (!player) { return; } if (player->thread_running) { LOGI("播放线程正在运行中..."); return; } // 判断播放列表是否为空、 pthread_mutex_lock(&player->playlist_mutex); bool has_media = (player->media_count > 0); pthread_mutex_unlock(&player->playlist_mutex); if (!has_media) { LOGD("播放线程退出,播放列表为空"); return; } if (player->stop_requested) { LOGD("播放线程退出,用户主动调用退出"); return; } // 检查线程是否已结束 void *value = pthread_getspecific(player->player_thread); if (value == (void *)1) { LOGI("播放器线程已退出"); // 计算距离上次播放时间 long long last_play_time_ms = player->last_play_time_ms; long long current_time_ms = get_current_time_ms(); // 距离最后一次播放时间 long long time_diff_ms = current_time_ms - last_play_time_ms; // 判断播放器是否停止;最后播放时间-当前播放视频的总时长+5秒。超过则说明播放意外退出。重新开始播放 // 如果距离上次播放时间小于视频总时长+5秒,则等待5秒后重试 if (time_diff_ms > (player->video_duration_ms + 5000)) { // 尝试开始播放 LOGI("播放线程退出,开始尝试恢复播放"); video_player_play(player); } } else { LOGI("播放器线程尚未退出"); } }