1620 lines
48 KiB
C
1620 lines
48 KiB
C
/*
|
||
* Copyright (c) 2025, LISTENAI
|
||
*
|
||
* SPDX-License-Identifier: Apache-2.0
|
||
*/
|
||
|
||
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include "lisa_mem.h"
|
||
#include "lisa_log.h"
|
||
#include "app_player_internal.h"
|
||
#include "app_player_core.h"
|
||
#include "pa_manager.h"
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
#include "app_player_focus.h"
|
||
#include "listen_audiomgr.h"
|
||
#endif
|
||
|
||
#define TAG "APP_PLAYER"
|
||
|
||
#define APP_PLAYER_VOL_MIN (1)
|
||
#define APP_PLAYER_VOL_MAX (100)
|
||
|
||
// 注意:互斥锁宏和数据结构已移到 app_player_internal.h
|
||
|
||
/**
|
||
* @brief 全局播放器 ID 计数器
|
||
*/
|
||
static uint32_t s_player_id_counter = 0;
|
||
|
||
/**
|
||
* @brief 全局 ID 计数器锁
|
||
*/
|
||
static SemaphoreHandle_t s_id_counter_lock = NULL;
|
||
|
||
/**
|
||
* @brief 全局模块初始化标志
|
||
*/
|
||
static bool s_app_player_initialized = false;
|
||
|
||
/**
|
||
* @brief 全局实例链表节点
|
||
*/
|
||
typedef struct player_instance_node {
|
||
app_player_t *player; /**< 播放器实例 */
|
||
struct player_instance_node *next; /**< 下一个节点 */
|
||
} player_instance_node_t;
|
||
|
||
/**
|
||
* @brief 全局实例链表头
|
||
*/
|
||
static player_instance_node_t *s_player_list_head = NULL;
|
||
|
||
/**
|
||
* @brief 全局实例链表锁
|
||
*/
|
||
static SemaphoreHandle_t s_player_list_lock = NULL;
|
||
|
||
// 注意:callback_event_t, callback_queue_t, callback_map_t, app_player_s 结构体
|
||
// 已移到 app_player_internal.h
|
||
|
||
/**
|
||
* @brief 初始化全局实例链表锁
|
||
*/
|
||
static void __init_player_list(void)
|
||
{
|
||
if (!s_player_list_lock) {
|
||
s_player_list_lock = PLAYER_MUTEX_CREATE();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 注册播放器实例到全局链表
|
||
* @param player 播放器实例
|
||
* @return 0成功,负数失败
|
||
*/
|
||
static int __register_player_instance(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
return -1;
|
||
}
|
||
|
||
__init_player_list();
|
||
|
||
player_instance_node_t *node = lisa_mem_calloc(1, sizeof(player_instance_node_t));
|
||
if (!node) {
|
||
LISA_LOGE(TAG, "Register instance failed: no memory");
|
||
return -1;
|
||
}
|
||
|
||
node->player = player;
|
||
|
||
PLAYER_MUTEX_LOCK(s_player_list_lock, LISA_OS_WAIT_FOREVER);
|
||
node->next = s_player_list_head;
|
||
s_player_list_head = node;
|
||
PLAYER_MUTEX_UNLOCK(s_player_list_lock);
|
||
|
||
LISA_LOGD(TAG, "Instance registered: id=%u", player->id);
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 从全局链表注销播放器实例
|
||
* @param player 播放器实例
|
||
*/
|
||
static void __unregister_player_instance(app_player_t *player)
|
||
{
|
||
if (!player || !s_player_list_lock) {
|
||
return;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(s_player_list_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
player_instance_node_t **curr = &s_player_list_head;
|
||
while (*curr) {
|
||
if ((*curr)->player == player) {
|
||
player_instance_node_t *to_free = *curr;
|
||
*curr = (*curr)->next;
|
||
lisa_mem_free(to_free);
|
||
LISA_LOGD(TAG, "Instance unregistered: id=%u", player->id);
|
||
break;
|
||
}
|
||
curr = &((*curr)->next);
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(s_player_list_lock);
|
||
}
|
||
|
||
/**
|
||
* @brief 根据ID查找播放器实例
|
||
* @param id 播放器ID
|
||
* @return 播放器实例指针,未找到返回NULL
|
||
*/
|
||
static app_player_t *__find_player_by_id(uint32_t id)
|
||
{
|
||
if (!s_player_list_lock) {
|
||
return NULL;
|
||
}
|
||
|
||
app_player_t *result = NULL;
|
||
|
||
PLAYER_MUTEX_LOCK(s_player_list_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
player_instance_node_t *curr = s_player_list_head;
|
||
while (curr) {
|
||
if (curr->player && curr->player->id == id) {
|
||
result = curr->player;
|
||
break;
|
||
}
|
||
curr = curr->next;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(s_player_list_lock);
|
||
|
||
return result;
|
||
}
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
/**
|
||
* @brief 通过焦点通道ID查找播放器实例(导出给 focus 模块使用)
|
||
* @param focus_channel_id 焦点通道ID
|
||
* @return 播放器实例指针,未找到返回 NULL
|
||
*/
|
||
app_player_t *__find_player_by_focus_channel_id(int focus_channel_id)
|
||
{
|
||
if (!s_player_list_lock || focus_channel_id < 0) {
|
||
return NULL;
|
||
}
|
||
|
||
app_player_t *result = NULL;
|
||
|
||
PLAYER_MUTEX_LOCK(s_player_list_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
player_instance_node_t *curr = s_player_list_head;
|
||
while (curr) {
|
||
if (curr->player && curr->player->focus_channel_id == focus_channel_id) {
|
||
result = curr->player;
|
||
break;
|
||
}
|
||
curr = curr->next;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(s_player_list_lock);
|
||
|
||
return result;
|
||
}
|
||
#endif // CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
|
||
/**
|
||
* @brief 初始化 app_player 模块
|
||
* @param config 初始化配置参数
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 必须在创建播放器实例前调用此函数
|
||
*/
|
||
int app_player_init(const app_player_config_t *config)
|
||
{
|
||
if (!config) {
|
||
LISA_LOGE(TAG, "Init failed: config is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (!config->pa_ctrl_callback) {
|
||
LISA_LOGE(TAG, "Init failed: pa_ctrl_callback is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (s_app_player_initialized) {
|
||
LISA_LOGW(TAG, "App player already initialized");
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Initializing app_player module");
|
||
|
||
// 初始化 PA 管理器
|
||
pa_manager_config_t pa_config = {
|
||
.ctrl_callback = config->pa_ctrl_callback
|
||
};
|
||
|
||
int ret = pa_manager_init(&pa_config);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "Init failed: pa_manager_init error %d", ret);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
lisa_player_set_loglev(2);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 初始化焦点管理器(如果配置了焦点通道)
|
||
if (config->focus_configs && config->focus_config_count > 0) {
|
||
ret = app_player_focus_init(config->focus_configs, config->focus_config_count);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "Init failed: app_player_focus_init error %d", ret);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
} else {
|
||
LISA_LOGI(TAG, "Audio focus disabled (no focus configs)");
|
||
}
|
||
#endif
|
||
|
||
s_app_player_initialized = true;
|
||
|
||
LISA_LOGI(TAG, "App player module initialized successfully");
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 分配唯一的播放器 ID(线程安全)
|
||
* @return 分配的 ID
|
||
*/
|
||
static uint32_t __alloc_player_id(void)
|
||
{
|
||
// 延迟初始化全局锁(首次调用时创建)
|
||
if (!s_id_counter_lock) {
|
||
s_id_counter_lock = PLAYER_MUTEX_CREATE();
|
||
if (!s_id_counter_lock) {
|
||
LISA_LOGE(TAG, "Failed to create ID counter lock");
|
||
return 0; // 返回 0 表示失败
|
||
}
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(s_id_counter_lock, LISA_OS_WAIT_FOREVER);
|
||
uint32_t id = s_player_id_counter++;
|
||
PLAYER_MUTEX_UNLOCK(s_id_counter_lock);
|
||
|
||
return id;
|
||
}
|
||
|
||
/**
|
||
* @brief 将lisa_player事件转换为app_player事件
|
||
* @param evt lisa_player事件
|
||
* @return app_player事件
|
||
*/
|
||
static app_player_event_t __convert_player_evt_to_app_event(PlayerEvt evt)
|
||
{
|
||
switch (evt) {
|
||
case PLAYER_EVT_PREPARED:
|
||
return APP_PLAYER_EVENT_PREPARED;
|
||
case PLAYER_EVT_PLAYING:
|
||
return APP_PLAYER_EVENT_PLAYING;
|
||
case PLAYER_EVT_PAUSED:
|
||
return APP_PLAYER_EVENT_PAUSED;
|
||
case PLAYER_EVT_STOPED:
|
||
return APP_PLAYER_EVENT_STOPPED;
|
||
case PLAYER_EVT_PLAYBACK_COMPLETE:
|
||
return APP_PLAYER_EVENT_COMPLETED;
|
||
case PLAYER_EVT_SEEK_COMPLETE:
|
||
return APP_PLAYER_EVENT_SEEK_COMPLETE;
|
||
case PLAYER_EVT_ERROR:
|
||
default:
|
||
return APP_PLAYER_EVENT_ERROR;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 将事件加入回调队列(导出给 core 模块使用)
|
||
* @param player 播放器实例
|
||
* @param event 事件类型
|
||
*/
|
||
void __enqueue_callback_event(app_player_t *player, app_player_event_t event)
|
||
{
|
||
if (!player || !player->cb_queue) {
|
||
return;
|
||
}
|
||
|
||
// 创建事件
|
||
callback_event_t *evt = lisa_mem_calloc(1, sizeof(callback_event_t));
|
||
if (!evt) {
|
||
LISA_LOGE(TAG, "Enqueue event failed: no memory");
|
||
return;
|
||
}
|
||
|
||
evt->event = event;
|
||
evt->next = NULL;
|
||
|
||
// 加入队列
|
||
callback_queue_t *queue = player->cb_queue;
|
||
PLAYER_MUTEX_LOCK(queue->lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
if (queue->tail) {
|
||
queue->tail->next = evt;
|
||
} else {
|
||
queue->head = evt;
|
||
}
|
||
queue->tail = evt;
|
||
|
||
PLAYER_MUTEX_UNLOCK(queue->lock);
|
||
|
||
// 通知回调线程
|
||
lisa_semaphore_give(queue->sem);
|
||
}
|
||
|
||
/**
|
||
* @brief 回调处理线程(每个实例独立)
|
||
* @param arg 播放器实例指针
|
||
*/
|
||
static void __callback_thread_func(void *arg)
|
||
{
|
||
app_player_t *player = (app_player_t *)arg;
|
||
if (!player || !player->cb_queue) {
|
||
return;
|
||
}
|
||
|
||
callback_queue_t *queue = player->cb_queue;
|
||
|
||
LISA_LOGI(TAG, "Callback thread started for player[%d] %s", player->id, player->name);
|
||
|
||
while (queue->running) {
|
||
// 等待事件
|
||
if (lisa_semaphore_take(queue->sem, LISA_OS_WAIT_FOREVER) != 0) {
|
||
continue;
|
||
}
|
||
|
||
while (1) {
|
||
// 从队列取出事件
|
||
PLAYER_MUTEX_LOCK(queue->lock, LISA_OS_WAIT_FOREVER);
|
||
callback_event_t *evt = queue->head;
|
||
if (evt) {
|
||
queue->head = evt->next;
|
||
if (!queue->head) {
|
||
queue->tail = NULL;
|
||
}
|
||
}
|
||
PLAYER_MUTEX_UNLOCK(queue->lock);
|
||
|
||
if (!evt) {
|
||
break; // 队列为空
|
||
}
|
||
|
||
// 读取回调信息
|
||
PLAYER_MUTEX_LOCK(player->cb_map.lock, LISA_OS_WAIT_FOREVER);
|
||
app_player_event_cb_t cb = player->cb_map.callback;
|
||
void *user_data = player->cb_map.user_data;
|
||
PLAYER_MUTEX_UNLOCK(player->cb_map.lock);
|
||
|
||
// 调用用户回调(不持锁)
|
||
if (cb) {
|
||
cb(player, evt->event, user_data);
|
||
}
|
||
|
||
// 释放事件内存
|
||
lisa_mem_free(evt);
|
||
}
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Callback thread stopped for player[%d] %s", player->id, player->name);
|
||
}
|
||
|
||
// 注意:__inner_player_pause/resume/stop, __execute_focus_loss_policy,
|
||
// __audio_focus_change_bridge 已移到 app_player_focus.c
|
||
|
||
/**
|
||
* @brief lisa_player回调处理函数(中间层,快速返回)
|
||
* @param evt lisa_player事件
|
||
* @param arg1 参数1
|
||
* @param arg2 参数2(保留)
|
||
* @param id 播放器ID
|
||
* @return 0成功,负数失败
|
||
*
|
||
* @note 关键逻辑:
|
||
* 1. PREPARED事件:根据标志决定是否自动播放
|
||
* 2. 播放结束事件:关闭PA
|
||
* 3. ERROR事件:自动reset播放器
|
||
* 4. 中断标志处理:唤醒等待的preparing信号量
|
||
*/
|
||
static int __lisa_player_callback_handler(PlayerEvt evt, int arg1, int arg2, int id)
|
||
{
|
||
// 通过ID查找实例
|
||
app_player_t *player = __find_player_by_id(id);
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Callback: player not found for id=%d", id);
|
||
return -1;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Player[%d] %s evt=%d", id, player->name, evt);
|
||
|
||
// 标志:是否忽略自动播放
|
||
bool ignore_play = false;
|
||
|
||
// 清除preparing标志
|
||
player->is_preparing = false;
|
||
|
||
// 如果正在等待中断,唤醒信号量
|
||
if (player->wait_prepare_intercepted) {
|
||
ignore_play = true;
|
||
lisa_semaphore_give(player->preparing_sem);
|
||
}
|
||
|
||
// 特殊事件处理(同步执行,快速返回)
|
||
switch (evt) {
|
||
case PLAYER_EVT_PREPARED: {
|
||
if (!ignore_play) {
|
||
if (!player->pause_preparing) {
|
||
// 自动开始播放
|
||
if (lisa_player_play(player->hld) == PLAYER_OP_FAIL) {
|
||
LISA_LOGE(TAG, "Auto play failed: %s", player->name);
|
||
// 播放失败,通知错误事件
|
||
__enqueue_callback_event(player, APP_PLAYER_EVENT_ERROR);
|
||
return 0;
|
||
}
|
||
} else {
|
||
// 准备时被暂停,直接通知暂停事件
|
||
LISA_LOGD(TAG, "Player %s has pause when preparing", player->name);
|
||
__enqueue_callback_event(player, APP_PLAYER_EVENT_PAUSED);
|
||
return 0;
|
||
}
|
||
}
|
||
// PREPARED事件不通知用户(因为会自动触发PLAYING)
|
||
return 0;
|
||
}
|
||
|
||
case PLAYER_EVT_PAUSED:
|
||
case PLAYER_EVT_STOPED:
|
||
case PLAYER_EVT_PLAYBACK_COMPLETE:
|
||
case PLAYER_EVT_ERROR: {
|
||
// 特殊情况:Stop后立即来Error,不需要再关闭PA
|
||
if (player->last_evt == PLAYER_EVT_STOPED && evt == PLAYER_EVT_ERROR) {
|
||
LISA_LOGD(TAG, "Player %s: skip PA off after stop+error", player->name);
|
||
} else {
|
||
// 播放结束,延迟关闭PA
|
||
pa_manager_control(0, CONFIG_APP_PLAYER_PA_OFF_DELAY_MS);
|
||
}
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 播放完成、停止、错误或用户主动暂停时释放焦点
|
||
// 注意:被焦点管理器暂停时(user_initiated_stop=false)不释放焦点,以便后续自动恢复
|
||
bool user_initiated = app_player_focus_is_user_initiated(player);
|
||
if (evt == PLAYER_EVT_STOPED || evt == PLAYER_EVT_PLAYBACK_COMPLETE ||
|
||
evt == PLAYER_EVT_ERROR || (evt == PLAYER_EVT_PAUSED && user_initiated)) {
|
||
LISA_LOGD(TAG, "Releasing audio focus for player %s (evt=%d, user_initiated=%d)",
|
||
player->name, evt, user_initiated);
|
||
bool is_user_initiated = user_initiated;
|
||
app_player_focus_release(player, is_user_initiated);
|
||
// 释放后清除用户主动标志
|
||
app_player_focus_set_user_initiated(player, false);
|
||
}
|
||
#endif
|
||
|
||
// ERROR事件需要自动reset
|
||
if (evt == PLAYER_EVT_ERROR) {
|
||
LISA_LOGW(TAG, "Player %s error, auto reset", player->name);
|
||
lisa_player_reset(player->hld);
|
||
}
|
||
|
||
// fall through,继续处理
|
||
break;
|
||
}
|
||
|
||
case PLAYER_EVT_PLAYING: {
|
||
// 播放中,确保PA开启
|
||
pa_manager_control(1, 0);
|
||
break;
|
||
}
|
||
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// 更新内部状态
|
||
player->last_evt = evt;
|
||
|
||
// 转换事件并加入队列(异步通知用户)
|
||
app_player_event_t app_evt = __convert_player_evt_to_app_event(evt);
|
||
__enqueue_callback_event(player, app_evt);
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* @brief 创建播放器实例
|
||
* @param name 播放器名称
|
||
* @return 播放器实例指针,失败返回 NULL
|
||
*/
|
||
app_player_t *app_player_create(const char *name)
|
||
{
|
||
if (!name) {
|
||
LISA_LOGE(TAG, "Create failed: name is NULL");
|
||
return NULL;
|
||
}
|
||
|
||
if (!s_app_player_initialized) {
|
||
LISA_LOGE(TAG, "Create failed: app_player not initialized, call app_player_init first");
|
||
return NULL;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Creating player: %s", name);
|
||
|
||
// 分配播放器结构体
|
||
app_player_t *player = (app_player_t *)lisa_mem_calloc(1, sizeof(app_player_t));
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Create failed: no memory for player");
|
||
return NULL;
|
||
}
|
||
|
||
// 复制名称
|
||
player->name = lisa_mem_alloc(strlen(name) + 1);
|
||
if (!player->name) {
|
||
LISA_LOGE(TAG, "Create failed: no memory for name");
|
||
goto _err;
|
||
}
|
||
strcpy(player->name, name);
|
||
|
||
// 分配唯一 ID(线程安全)
|
||
player->id = __alloc_player_id();
|
||
|
||
// 创建实例操作互斥锁
|
||
player->operation_lock = PLAYER_MUTEX_CREATE();
|
||
if (!player->operation_lock) {
|
||
LISA_LOGE(TAG, "Create failed: operation_lock create failed");
|
||
goto _err;
|
||
}
|
||
|
||
// 创建核心层操作互斥锁
|
||
player->core_lock = PLAYER_MUTEX_CREATE();
|
||
if (!player->core_lock) {
|
||
LISA_LOGE(TAG, "Create failed: core_lock create failed");
|
||
goto _err;
|
||
}
|
||
|
||
// 创建底层播放器句柄
|
||
player->hld = lisa_player_create(name, player->id);
|
||
if (!player->hld) {
|
||
LISA_LOGE(TAG, "Create failed: lisa_player_create failed");
|
||
goto _err;
|
||
}
|
||
|
||
// 初始化回调管理
|
||
player->cb_map.lock = PLAYER_MUTEX_CREATE();
|
||
if (!player->cb_map.lock) {
|
||
LISA_LOGE(TAG, "Create failed: mutex create failed");
|
||
goto _err;
|
||
}
|
||
player->cb_map.callback = NULL;
|
||
player->cb_map.user_data = NULL;
|
||
|
||
// 创建准备信号量
|
||
player->preparing_sem = lisa_semaphore_create(1);
|
||
if (!player->preparing_sem) {
|
||
LISA_LOGE(TAG, "Create failed: semaphore create failed");
|
||
goto _err;
|
||
}
|
||
|
||
// 初始化状态
|
||
player->state = APP_PLAYER_STATE_IDLE;
|
||
player->last_evt = PLAYER_EVT_INIT;
|
||
player->is_preparing = false;
|
||
player->wait_prepare_intercepted = false;
|
||
player->pause_preparing = false;
|
||
player->is_stream_mode = false;
|
||
|
||
// 初始化音量范围
|
||
player->vol_min = APP_PLAYER_VOL_MIN;
|
||
player->vol_max = APP_PLAYER_VOL_MAX;
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 初始化焦点相关字段
|
||
player->focus_channel_id = -1;
|
||
player->focus_cb = NULL;
|
||
player->focus_user_data = NULL;
|
||
player->focus_cb_lock = PLAYER_MUTEX_CREATE();
|
||
if (!player->focus_cb_lock) {
|
||
LISA_LOGE(TAG, "Create failed: focus_cb_lock create failed");
|
||
goto _err;
|
||
}
|
||
player->paused_by_focus = false;
|
||
app_player_focus_set_user_initiated(player, false);
|
||
player->last_focus_state = APP_PLAYER_FOCUS_NONE;
|
||
// behavior 将在注册焦点通道时从配置中复制
|
||
#endif
|
||
|
||
// 创建回调事件队列
|
||
player->cb_queue = lisa_mem_calloc(1, sizeof(callback_queue_t));
|
||
if (!player->cb_queue) {
|
||
LISA_LOGE(TAG, "Create failed: no memory for callback queue");
|
||
goto _err;
|
||
}
|
||
|
||
player->cb_queue->lock = PLAYER_MUTEX_CREATE();
|
||
if (!player->cb_queue->lock) {
|
||
LISA_LOGE(TAG, "Create failed: callback queue mutex create failed");
|
||
goto _err;
|
||
}
|
||
|
||
player->cb_queue->sem = lisa_semaphore_create(1);
|
||
if (!player->cb_queue->sem) {
|
||
LISA_LOGE(TAG, "Create failed: callback queue semaphore create failed");
|
||
goto _err;
|
||
}
|
||
|
||
player->cb_queue->head = NULL;
|
||
player->cb_queue->tail = NULL;
|
||
player->cb_queue->running = true;
|
||
|
||
// 创建回调处理线程
|
||
char thread_name[32];
|
||
snprintf(thread_name, sizeof(thread_name), "cb_%s", name);
|
||
lisa_thread_attr_t thread_attr = {
|
||
.name = (uint8_t *)thread_name,
|
||
.stack_size = CONFIG_APP_PLAYER_CALLBACK_THREAD_STACK_SIZE,
|
||
.priority = CONFIG_APP_PLAYER_CALLBACK_THREAD_PRIORITY
|
||
};
|
||
player->cb_thread = lisa_thread_create(&thread_attr,
|
||
__callback_thread_func,
|
||
player);
|
||
if (!player->cb_thread) {
|
||
LISA_LOGE(TAG, "Create failed: callback thread create failed");
|
||
player->cb_queue->running = false;
|
||
goto _err;
|
||
}
|
||
|
||
// 注册lisa_player回调
|
||
if (lisa_player_set_callback(player->hld, __lisa_player_callback_handler) != PLAYER_OK) {
|
||
LISA_LOGE(TAG, "Create failed: set lisa_player callback failed");
|
||
player->cb_queue->running = false;
|
||
lisa_semaphore_give(player->cb_queue->sem); // 唤醒线程退出
|
||
player->cb_thread = NULL;
|
||
goto _err;
|
||
}
|
||
|
||
// 注册实例到全局链表
|
||
if (__register_player_instance(player) != 0) {
|
||
LISA_LOGE(TAG, "Create failed: register instance failed");
|
||
player->cb_queue->running = false;
|
||
lisa_semaphore_give(player->cb_queue->sem); // 唤醒线程退出
|
||
player->cb_thread = NULL;
|
||
goto _err;
|
||
}
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 注册焦点通道(如果焦点管理器已初始化且配置了对应通道)
|
||
int ret = app_player_focus_register(player, name);
|
||
if (ret != 0 && ret != -2) { // -2 表示没有找到配置,可以忽略
|
||
LISA_LOGW(TAG, "Failed to register player %s to focus manager: %d", name, ret);
|
||
}
|
||
#endif
|
||
|
||
LISA_LOGI(TAG, "Player created successfully: %s (id=%u, handle=%p)", name, player->id, player);
|
||
|
||
return player;
|
||
|
||
_err:
|
||
// 统一错误处理:按相反顺序清理资源
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
if (player->focus_cb_lock) {
|
||
PLAYER_MUTEX_DELETE(player->focus_cb_lock);
|
||
}
|
||
#endif
|
||
|
||
if (player->cb_queue) {
|
||
if (player->cb_queue->sem) {
|
||
lisa_semaphore_delete(player->cb_queue->sem);
|
||
}
|
||
if (player->cb_queue->lock) {
|
||
PLAYER_MUTEX_DELETE(player->cb_queue->lock);
|
||
}
|
||
lisa_mem_free(player->cb_queue);
|
||
}
|
||
|
||
if (player->preparing_sem) {
|
||
lisa_semaphore_delete(player->preparing_sem);
|
||
}
|
||
|
||
if (player->cb_map.lock) {
|
||
PLAYER_MUTEX_DELETE(player->cb_map.lock);
|
||
}
|
||
|
||
if (player->hld) {
|
||
lisa_player_destory(player->hld);
|
||
}
|
||
|
||
if (player->operation_lock) {
|
||
PLAYER_MUTEX_DELETE(player->operation_lock);
|
||
}
|
||
|
||
if (player->core_lock) {
|
||
PLAYER_MUTEX_DELETE(player->core_lock);
|
||
}
|
||
|
||
if (player->name) {
|
||
lisa_mem_free(player->name);
|
||
}
|
||
|
||
lisa_mem_free(player);
|
||
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* @brief 注册事件回调
|
||
* @param player 播放器实例
|
||
* @param event_cb 事件回调函数
|
||
* @param user_data 用户数据
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 建议在首次播放前调用
|
||
*/
|
||
int app_player_register_callback(app_player_t *player, app_player_event_cb_t event_cb, void *user_data)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Register callback failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (!event_cb) {
|
||
LISA_LOGE(TAG, "Register callback failed: event_cb is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Registering callback for player: %s (id=%u)", player->name, player->id);
|
||
|
||
// 加锁保护回调注册
|
||
PLAYER_MUTEX_LOCK(player->cb_map.lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
player->cb_map.callback = event_cb;
|
||
player->cb_map.user_data = user_data;
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->cb_map.lock);
|
||
|
||
LISA_LOGD(TAG, "Callback registered successfully: %s (cb=%p, user_data=%p)",
|
||
player->name, event_cb, user_data);
|
||
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
// 注意:__player_prepare_check 已移到 app_player_core.c 作为 app_player_core_prepare_check
|
||
|
||
/**
|
||
* @brief 播放音频(扩展版本,支持播放选项)
|
||
* @param player 播放器实例
|
||
* @param opt 播放选项
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 支持URL播放和流式播放模式
|
||
*/
|
||
int app_player_play_ex(app_player_t *player, const app_player_play_opt_t *opt)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Play failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (!opt) {
|
||
LISA_LOGE(TAG, "Play failed: opt is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Play ex: %s, url=%s, throw_time=%d",
|
||
player->name,
|
||
opt->url ? opt->url : "NULL",
|
||
opt->throw_time_ms);
|
||
|
||
if (!opt->url) {
|
||
LISA_LOGE(TAG, "Play failed: url is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 清除焦点暂停标志(用户主动播放)
|
||
app_player_focus_set_paused_by_focus(player, false);
|
||
|
||
// 申请音频焦点(如果已注册焦点通道)
|
||
app_player_focus_acquire(player);
|
||
#endif
|
||
|
||
// 开启 PA
|
||
pa_manager_control(1, 0);
|
||
|
||
// 调用 core 层播放(纯播放控制)
|
||
int ret = app_player_core_play(player, opt->url, opt->throw_time_ms);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "Play failed: app_player_core_play error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_IO;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Play started: %s", player->name);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 播放音频(简化版本)
|
||
* @param player 播放器实例
|
||
* @param url 音频资源URL
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
*/
|
||
int app_player_play(app_player_t *player, const char *url)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Play failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (!url) {
|
||
LISA_LOGE(TAG, "Play failed: url is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 构造播放选项
|
||
app_player_play_opt_t opt = {
|
||
.url = url,
|
||
.throw_time_ms = 0,
|
||
};
|
||
|
||
return app_player_play_ex(player, &opt);
|
||
}
|
||
|
||
/**
|
||
* @brief 停止播放(异步)
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 异步停止,立即返回,停止完成后会收到STOPPED事件
|
||
*/
|
||
int app_player_stop(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Stop failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 流式播放模式下不支持 stop 操作
|
||
if (player->is_stream_mode) {
|
||
LISA_LOGE(TAG, "Stop failed: not supported in stream mode");
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Stop: %s", player->name);
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 标记为用户主动stop,后续焦点释放时跳过策略执行
|
||
app_player_focus_set_user_initiated(player, true);
|
||
#endif
|
||
|
||
// 关闭PA
|
||
pa_manager_control(0, CONFIG_APP_PLAYER_PA_OFF_DELAY_MS);
|
||
|
||
// 调用 core 层停止(纯播放控制)
|
||
int ret = app_player_core_stop(player);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "Stop failed: app_player_core_stop error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 用户主动停止,释放焦点(在锁外执行)
|
||
app_player_focus_release(player, true);
|
||
#endif
|
||
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 停止播放(同步,等待停止完成)
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 同步停止,等待停止完成后返回
|
||
*/
|
||
int app_player_stop_sync(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Stop sync failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 流式播放模式下不支持 stop 操作
|
||
if (player->is_stream_mode) {
|
||
LISA_LOGE(TAG, "Stop sync failed: not supported in stream mode");
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Stop sync: %s", player->name);
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 标记为用户主动stop,后续焦点释放时跳过策略执行
|
||
app_player_focus_set_user_initiated(player, true);
|
||
#endif
|
||
|
||
// 关闭PA
|
||
pa_manager_control(0, CONFIG_APP_PLAYER_PA_OFF_DELAY_MS);
|
||
|
||
// 调用 core 层同步停止(纯播放控制)
|
||
int ret = app_player_core_stop_sync(player);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "%s Stop sync failed: app_player_core_stop_sync error %d", player->name, ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 用户主动停止,释放焦点(在锁外执行)
|
||
app_player_focus_release(player, true);
|
||
#endif
|
||
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 暂停播放
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 如果正在准备阶段,则设置pause_preparing标志,准备完成后不会自动播放
|
||
*/
|
||
int app_player_pause(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Pause failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 流式播放模式下不支持 pause 操作
|
||
if (player->is_stream_mode) {
|
||
LISA_LOGE(TAG, "Pause failed: not supported in stream mode");
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
// 检查当前状态,如果已经是PAUSED,无需重复pause
|
||
PlayerState state = lisa_player_get_state(player->hld);
|
||
if (state == PLAYER_ST_PAUSED) {
|
||
LISA_LOGI(TAG, "Pause: %s already paused, skip", player->name);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Pause: %s", player->name);
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 标记为用户主动pause,后续焦点释放时跳过策略执行
|
||
app_player_focus_set_user_initiated(player, true);
|
||
#endif
|
||
|
||
// 关闭PA
|
||
pa_manager_control(0, CONFIG_APP_PLAYER_PA_OFF_DELAY_MS);
|
||
|
||
// 调用 core 层暂停(纯播放控制)
|
||
int ret = app_player_core_pause(player);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "Pause failed: app_player_core_pause error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 用户主动暂停,释放焦点(在锁外执行)
|
||
app_player_focus_release(player, true);
|
||
#endif
|
||
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 恢复播放(异步)
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 如果在准备阶段被暂停,则清除pause_preparing标志并开始播放
|
||
*/
|
||
int app_player_resume(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Resume failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 流式播放模式下不支持 resume 操作
|
||
if (player->is_stream_mode) {
|
||
LISA_LOGE(TAG, "Resume failed: not supported in stream mode");
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Resume: %s", player->name);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 用户主动恢复,申请焦点
|
||
app_player_focus_acquire(player);
|
||
app_player_focus_set_paused_by_focus(player, false);
|
||
#endif
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 开启PA
|
||
pa_manager_control(1, 0);
|
||
|
||
// 调用 core 层恢复(纯播放控制)
|
||
int ret = app_player_core_resume(player);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "Resume failed: app_player_core_resume error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 恢复播放(同步,等待恢复完成)
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 同步版本,等待恢复完成后返回
|
||
*/
|
||
int app_player_resume_sync(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Resume sync failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 流式播放模式下不支持 resume 操作
|
||
if (player->is_stream_mode) {
|
||
LISA_LOGE(TAG, "Resume sync failed: not supported in stream mode");
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Resume sync: %s", player->name);
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 清除焦点暂停标志(用户主动恢复)
|
||
player->paused_by_focus = false;
|
||
#endif
|
||
|
||
// 开启PA
|
||
pa_manager_control(1, 0);
|
||
|
||
// 如果是在准备阶段被暂停的,清除pause_preparing标志并开始播放
|
||
if (player->pause_preparing) {
|
||
LISA_LOGD(TAG, "Player %s was paused during preparing, start playing", player->name);
|
||
player->pause_preparing = false;
|
||
PlayerErr ret = lisa_player_play(player->hld);
|
||
if (ret != PLAYER_OK) {
|
||
LISA_LOGE(TAG, "Resume sync failed: lisa_player_play error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
} else {
|
||
// 否则调用底层的同步恢复接口
|
||
PlayerErr ret = lisa_player_resume_sync(player->hld);
|
||
if (ret != PLAYER_OK) {
|
||
LISA_LOGE(TAG, "Resume sync failed: lisa_player_resume_sync error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 重置播放器到初始状态
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 重置后播放器回到IDLE状态,清除所有播放相关的标志和状态
|
||
*/
|
||
int app_player_reset(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Reset failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
LISA_LOGI(TAG, "Reset: %s", player->name);
|
||
|
||
// 清除准备中标志
|
||
player->is_preparing = false;
|
||
player->wait_prepare_intercepted = false;
|
||
player->pause_preparing = false;
|
||
|
||
// 调用底层reset
|
||
PlayerErr ret = lisa_player_reset(player->hld);
|
||
if (ret != PLAYER_OK) {
|
||
LISA_LOGE(TAG, "Reset failed: lisa_player_reset error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
// 更新状态为IDLE
|
||
player->state = APP_PLAYER_STATE_IDLE;
|
||
player->last_evt = PLAYER_EVT_INIT;
|
||
|
||
LISA_LOGD(TAG, "Player %s reset to IDLE state", player->name);
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 跳转到指定位置
|
||
* @param player 播放器实例
|
||
* @param seek_ms 跳转位置(毫秒)
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 异步操作,跳转完成后会收到 SEEK_COMPLETE 事件
|
||
*/
|
||
int app_player_seek(app_player_t *player, uint32_t seek_ms)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Seek failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 流式播放模式下不支持 seek 操作
|
||
if (player->is_stream_mode) {
|
||
LISA_LOGE(TAG, "Seek failed: not supported in stream mode");
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
LISA_LOGI(TAG, "Seek: %s to %u ms", player->name, seek_ms);
|
||
|
||
// 调用底层seek
|
||
PlayerErr ret = lisa_player_seek(player->hld, seek_ms);
|
||
if (ret != PLAYER_OK) {
|
||
LISA_LOGE(TAG, "Seek failed: lisa_player_seek error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取播放器状态
|
||
* @param player 播放器实例
|
||
* @return 播放器状态
|
||
*/
|
||
app_player_state_t app_player_get_state(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Get state failed: player is NULL");
|
||
return APP_PLAYER_STATE_ERROR;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 获取底层播放器状态
|
||
PlayerState state = lisa_player_get_state(player->hld);
|
||
|
||
// 转换为app_player状态
|
||
app_player_state_t app_state;
|
||
switch (state) {
|
||
case PLAYER_ST_NONE:
|
||
app_state = APP_PLAYER_STATE_IDLE;
|
||
break;
|
||
case PLAYER_ST_READY_TO_PLAY:
|
||
app_state = APP_PLAYER_STATE_PREPARING;
|
||
break;
|
||
case PLAYER_ST_PREPARED:
|
||
app_state = APP_PLAYER_STATE_PREPARED;
|
||
break;
|
||
case PLAYER_ST_PLAYING:
|
||
app_state = APP_PLAYER_STATE_PLAYING;
|
||
break;
|
||
case PLAYER_ST_PAUSED:
|
||
app_state = APP_PLAYER_STATE_PAUSED;
|
||
break;
|
||
case PLAYER_ST_STOPED:
|
||
app_state = APP_PLAYER_STATE_STOPPED;
|
||
break;
|
||
case PLAYER_ST_PLAYBACK_COMPLETE:
|
||
app_state = APP_PLAYER_STATE_STOPPED;
|
||
break;
|
||
case PLAYER_ST_ERROR:
|
||
default:
|
||
app_state = APP_PLAYER_STATE_ERROR;
|
||
break;
|
||
}
|
||
|
||
// 更新内部状态缓存
|
||
player->state = app_state;
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
|
||
return app_state;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取当前播放位置
|
||
* @param player 播放器实例
|
||
* @param position 输出参数,当前位置(毫秒)
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
*/
|
||
int app_player_get_position(app_player_t *player, uint32_t *position)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Get position failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (!position) {
|
||
LISA_LOGE(TAG, "Get position failed: position is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 获取底层播放位置
|
||
int32_t pos = lisa_player_get_pos(player->hld);
|
||
if (pos < 0) {
|
||
LISA_LOGE(TAG, "Get position failed: lisa_player_get_pos error %d", pos);
|
||
*position = 0;
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
*position = (uint32_t)pos;
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 获取总时长
|
||
* @param player 播放器实例
|
||
* @param duration 输出参数,总时长(毫秒)
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
*/
|
||
int app_player_get_duration(app_player_t *player, uint32_t *duration)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Get duration failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (!duration) {
|
||
LISA_LOGE(TAG, "Get duration failed: duration is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 获取底层播放时长
|
||
int32_t dur = lisa_player_get_duration(player->hld);
|
||
if (dur < 0) {
|
||
LISA_LOGE(TAG, "Get duration failed: lisa_player_get_duration error %d", dur);
|
||
*duration = 0;
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
*duration = (uint32_t)dur;
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 设置音量
|
||
* @param player 播放器实例
|
||
* @param volume 音量值(1-100)
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 音量会根据播放器的音量范围进行映射转换
|
||
*/
|
||
int app_player_set_volume(app_player_t *player, uint8_t volume)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Set volume failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 检查音量范围
|
||
if (volume < APP_PLAYER_VOL_MIN || volume > APP_PLAYER_VOL_MAX) {
|
||
LISA_LOGE(TAG, "Set volume failed: volume %d out of range [%d, %d]",
|
||
volume, APP_PLAYER_VOL_MIN, APP_PLAYER_VOL_MAX);
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Set volume: %d", volume);
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 调用底层设置音量
|
||
PlayerErr ret = lisa_player_set_vol(player->hld, volume);
|
||
if (ret != PLAYER_OK) {
|
||
LISA_LOGE(TAG, "Set volume failed: lisa_player_set_vol error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 开始流式播放
|
||
* @param player 播放器实例
|
||
* @param sample_rate 采样率(Hz,如 8000, 16000, 48000)
|
||
* @param channels 声道数(1=单声道, 2=立体声)
|
||
* @param bits 位深度(8, 16, 24, 32)
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 调用此函数后,使用 app_player_write_stream 写入 PCM 数据
|
||
*/
|
||
int app_player_play_stream(app_player_t *player, uint32_t sample_rate, uint8_t channels, uint8_t bits)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Play stream failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 参数验证
|
||
if (sample_rate == 0) {
|
||
LISA_LOGE(TAG, "Play stream failed: invalid sample rate 0");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (channels != 1) {
|
||
LISA_LOGE(TAG, "Play stream failed: only mono channel supported (channels=%u)", channels);
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
if (bits != 16) {
|
||
LISA_LOGE(TAG, "Play stream failed: only 16-bit supported (bits=%u)", bits);
|
||
return APP_PLAYER_ERR_NOT_SUPPORTED;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Play stream: %s, rate=%u, ch=%u, bits=%u",
|
||
player->name, sample_rate, channels, bits);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 清除焦点暂停标志(用户主动播放)
|
||
app_player_focus_set_paused_by_focus(player, false);
|
||
|
||
// 申请音频焦点(如果已注册焦点通道)
|
||
app_player_focus_acquire(player);
|
||
#endif
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 开启PA
|
||
pa_manager_control(1, 0);
|
||
|
||
// 调用 core 层流式播放(纯播放控制)
|
||
int ret = app_player_core_play_stream(player, sample_rate, channels, bits);
|
||
if (ret != 0) {
|
||
LISA_LOGE(TAG, "Play stream failed: app_player_core_play_stream error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_IO;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Stream playback started: %s", player->name);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
/**
|
||
* @brief 写入流数据
|
||
* @param player 播放器实例
|
||
* @param data 数据指针
|
||
* @param size 数据大小(字节)
|
||
* @param timeout_ms 超时时间(毫秒)
|
||
* @return 实际写入的字节数,<0 表示错误
|
||
* @note 必须先调用 app_player_play_stream 开启流式播放
|
||
*/
|
||
int app_player_write_stream(app_player_t *player, const uint8_t *data, uint32_t size, uint32_t timeout_ms)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Write stream failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
if (!data && size > 0) {
|
||
LISA_LOGE(TAG, "Write stream failed: data is NULL but size > 0");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 调用 core 层写入流数据(纯播放控制)
|
||
int ret = app_player_core_stream_write(player, data, size, timeout_ms);
|
||
if (ret < 0) {
|
||
LISA_LOGE(TAG, "Write stream failed: app_player_core_stream_write error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_IO;
|
||
}
|
||
|
||
LISA_LOGD(TAG, "Stream data written: %d bytes", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* @brief 结束流式播放
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
* @note 通知播放器所有流数据已写入完毕,等待播放完成
|
||
*/
|
||
int app_player_finish_stream(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Finish stream failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
PLAYER_MUTEX_LOCK(player->operation_lock, LISA_OS_WAIT_FOREVER);
|
||
|
||
// 检查是否处于流式播放模式
|
||
if (!player->is_stream_mode) {
|
||
LISA_LOGE(TAG, "Finish stream failed: not in stream mode");
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Finish stream: %s", player->name);
|
||
|
||
// 发送结束信号(data=NULL, size=0)
|
||
int ret = lisa_player_put_stream_data(player->hld, NULL, 0, 0);
|
||
if (ret < 0) {
|
||
LISA_LOGE(TAG, "Finish stream failed: lisa_player_put_stream_data error %d", ret);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_ERR_IO;
|
||
}
|
||
|
||
// 清除流式模式标志
|
||
player->is_stream_mode = false;
|
||
|
||
LISA_LOGI(TAG, "Stream playback finished: %s", player->name);
|
||
PLAYER_MUTEX_UNLOCK(player->operation_lock);
|
||
return APP_PLAYER_OK;
|
||
}
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
/**
|
||
* @brief 注册播放器焦点变化回调
|
||
* @param player 播放器实例
|
||
* @param on_focus_change 焦点状态变化回调函数(可为 NULL 清除回调)
|
||
* @param user_data 用户自定义数据(传递给回调)
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
*/
|
||
int app_player_register_focus_cb(app_player_t *player,
|
||
app_player_focus_change_cb_t on_focus_change,
|
||
void *user_data)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Register focus callback failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
// 检查播放器是否已注册焦点通道
|
||
if (player->focus_channel_id < 0) {
|
||
LISA_LOGW(TAG, "Register focus callback failed: player %s not registered to focus manager",
|
||
player->name);
|
||
return APP_PLAYER_ERR_INVALID_STATE;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Registering focus callback for player: %s (id=%u)", player->name, player->id);
|
||
|
||
// 加锁保护焦点回调注册
|
||
PLAYER_MUTEX_LOCK(player->focus_cb_lock, LISA_OS_WAIT_FOREVER);
|
||
player->focus_cb = on_focus_change;
|
||
player->focus_user_data = user_data;
|
||
PLAYER_MUTEX_UNLOCK(player->focus_cb_lock);
|
||
|
||
LISA_LOGD(TAG, "Focus callback registered successfully: %s (cb=%p, user_data=%p)",
|
||
player->name, on_focus_change, user_data);
|
||
|
||
return APP_PLAYER_OK;
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* @brief 销毁播放器实例
|
||
* @param player 播放器实例
|
||
* @return APP_PLAYER_OK 成功,其他表示错误
|
||
*/
|
||
int app_player_destroy(app_player_t *player)
|
||
{
|
||
if (!player) {
|
||
LISA_LOGE(TAG, "Destroy failed: player is NULL");
|
||
return APP_PLAYER_ERR_INVALID_PARAM;
|
||
}
|
||
|
||
LISA_LOGI(TAG, "Destroying player: %s (id=%u)", player->name, player->id);
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 注销焦点通道
|
||
app_player_focus_unregister(player);
|
||
#endif
|
||
|
||
// 从全局链表注销实例
|
||
__unregister_player_instance(player);
|
||
|
||
// 停止回调线程
|
||
if (player->cb_queue) {
|
||
player->cb_queue->running = false;
|
||
lisa_semaphore_give(player->cb_queue->sem); // 唤醒线程退出
|
||
player->cb_thread = NULL;
|
||
}
|
||
|
||
// 清空并销毁回调队列
|
||
if (player->cb_queue) {
|
||
// 清空队列中的所有事件
|
||
PLAYER_MUTEX_LOCK(player->cb_queue->lock, LISA_OS_WAIT_FOREVER);
|
||
callback_event_t *evt = player->cb_queue->head;
|
||
while (evt) {
|
||
callback_event_t *next = evt->next;
|
||
lisa_mem_free(evt);
|
||
evt = next;
|
||
}
|
||
player->cb_queue->head = NULL;
|
||
player->cb_queue->tail = NULL;
|
||
PLAYER_MUTEX_UNLOCK(player->cb_queue->lock);
|
||
|
||
// 销毁队列资源
|
||
if (player->cb_queue->sem) {
|
||
lisa_semaphore_delete(player->cb_queue->sem);
|
||
}
|
||
if (player->cb_queue->lock) {
|
||
PLAYER_MUTEX_DELETE(player->cb_queue->lock);
|
||
}
|
||
lisa_mem_free(player->cb_queue);
|
||
player->cb_queue = NULL;
|
||
}
|
||
|
||
// 销毁底层播放器
|
||
if (player->hld) {
|
||
lisa_player_destory(player->hld);
|
||
player->hld = NULL;
|
||
}
|
||
|
||
// 销毁准备信号量
|
||
if (player->preparing_sem) {
|
||
lisa_semaphore_delete(player->preparing_sem);
|
||
player->preparing_sem = NULL;
|
||
}
|
||
|
||
// 销毁回调管理锁
|
||
if (player->cb_map.lock) {
|
||
PLAYER_MUTEX_DELETE(player->cb_map.lock);
|
||
player->cb_map.lock = NULL;
|
||
}
|
||
|
||
#ifdef CONFIG_APP_PLAYER_AUDIO_FOCUS
|
||
// 销毁焦点回调锁
|
||
if (player->focus_cb_lock) {
|
||
PLAYER_MUTEX_DELETE(player->focus_cb_lock);
|
||
player->focus_cb_lock = NULL;
|
||
}
|
||
#endif
|
||
|
||
// 销毁实例操作互斥锁
|
||
if (player->operation_lock) {
|
||
PLAYER_MUTEX_DELETE(player->operation_lock);
|
||
player->operation_lock = NULL;
|
||
}
|
||
|
||
// 销毁核心层操作互斥锁
|
||
if (player->core_lock) {
|
||
PLAYER_MUTEX_DELETE(player->core_lock);
|
||
player->core_lock = NULL;
|
||
}
|
||
|
||
// 释放名称内存
|
||
if (player->name) {
|
||
LISA_LOGI(TAG, "Player destroyed: %s (id=%u)", player->name, player->id);
|
||
lisa_mem_free(player->name);
|
||
player->name = NULL;
|
||
}
|
||
|
||
// 释放播放器结构体
|
||
lisa_mem_free(player);
|
||
|
||
return APP_PLAYER_OK;
|
||
}
|