mirror of
https://github.com/Vita3K/Vita3K.git
synced 2025-04-02 11:02:10 -04:00
523 lines
20 KiB
C++
523 lines
20 KiB
C++
// Vita3K emulator project
|
|
// Copyright (C) 2025 Vita3K team
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program; if not, write to the Free Software Foundation, Inc.,
|
|
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
#include <module/module.h>
|
|
|
|
#include <codec/state.h>
|
|
#include <io/functions.h>
|
|
#include <kernel/state.h>
|
|
|
|
#include <util/lock_and_find.h>
|
|
#include <util/log.h>
|
|
|
|
#include <algorithm>
|
|
|
|
// Defines stop/pause behaviour. If true, GetVideo/AudioData will return false when stopped.
|
|
constexpr bool REJECT_DATA_ON_PAUSE = true;
|
|
|
|
// Uses a catchup video style if lag causes the video to go behind.
|
|
constexpr bool CATCHUP_VIDEO_PLAYBACK = true;
|
|
|
|
/*
|
|
typedef Ptr<void> (*SceAvPlayerAllocator)(uint32_t arguments, uint32_t alignment, uint32_t size);
|
|
typedef void (*SceAvPlayerDeallocator)(uint32_t arguments, Ptr<void> memory);
|
|
|
|
typedef int32_t (*SceAvPlayerOpenFile)(uint32_t arguments, Ptr<const char> filename);
|
|
typedef int32_t (*SceAvPlayerCloseFile)(uint32_t arguments);
|
|
typedef int32_t (*SceAvPlayerReadFile)(uint32_t arguments, Ptr<uint8_t> buffer, uint64_t offset, uint32_t size);
|
|
typedef uint64_t (*SceAvPlayerGetFileSize)(uint32_t arguments);
|
|
|
|
typedef void (*SceAvPlayerEventCallback)(uint32_t arguments, int32_t event_id, int32_t source_id, Ptr<void> event_data);
|
|
*/
|
|
|
|
struct PlayerInfoState;
|
|
typedef std::shared_ptr<PlayerInfoState> PlayerPtr;
|
|
typedef std::map<SceUID, PlayerPtr> PlayerStates;
|
|
|
|
struct AvPlayerState {
|
|
std::mutex mutex;
|
|
PlayerStates players;
|
|
};
|
|
|
|
struct SceAvPlayerMemoryAllocator {
|
|
uint32_t user_data = 0;
|
|
|
|
// All of these should be cast to SceAvPlayerAllocator or SceAvPlayerDeallocator types.
|
|
Ptr<void> general_allocator;
|
|
Ptr<void> general_deallocator;
|
|
Ptr<void> texture_allocator;
|
|
Ptr<void> texture_deallocator;
|
|
};
|
|
|
|
struct SceAvPlayerFileManager {
|
|
uint32_t user_data = 0;
|
|
|
|
// Cast to SceAvPlayerOpenFile, SceAvPlayerCloseFile, SceAvPlayerReadFile and SceAvPlayerGetFileSize.
|
|
Ptr<void> open_file;
|
|
Ptr<void> close_file;
|
|
Ptr<void> read_file;
|
|
Ptr<void> file_size;
|
|
};
|
|
|
|
struct SceAvPlayerEventManager {
|
|
uint32_t user_data = 0;
|
|
|
|
// Cast to SceAvPlayerEventCallback.
|
|
Ptr<void> event_callback;
|
|
};
|
|
|
|
struct PlayerInfoState {
|
|
PlayerState player;
|
|
|
|
// Framebuffer count is defined in info. I'm being safe now and forcing it to 4 (even though its usually 2).
|
|
constexpr static uint32_t RING_BUFFER_COUNT = 4;
|
|
|
|
uint32_t video_buffer_ring_index = 0;
|
|
uint32_t video_buffer_size = 0;
|
|
std::array<Ptr<uint8_t>, RING_BUFFER_COUNT> video_buffer;
|
|
|
|
uint32_t audio_buffer_ring_index = 0;
|
|
uint32_t audio_buffer_size = 0;
|
|
std::array<Ptr<uint8_t>, RING_BUFFER_COUNT> audio_buffer;
|
|
|
|
bool do_loop = false;
|
|
bool paused = false;
|
|
|
|
uint64_t last_frame_time = 0;
|
|
SceAvPlayerMemoryAllocator memory_allocator;
|
|
SceAvPlayerFileManager file_manager;
|
|
SceAvPlayerEventManager event_manager;
|
|
};
|
|
|
|
enum class DebugLevel {
|
|
NONE,
|
|
INFO,
|
|
WARNINGS,
|
|
ALL,
|
|
};
|
|
|
|
struct SceAvPlayerInfo {
|
|
SceAvPlayerMemoryAllocator memory_allocator;
|
|
SceAvPlayerFileManager file_manager;
|
|
SceAvPlayerEventManager event_manager;
|
|
DebugLevel debug_level;
|
|
uint32_t base_priority;
|
|
int32_t frame_buffer_count;
|
|
int32_t auto_start;
|
|
uint32_t unknown0;
|
|
};
|
|
|
|
struct SceAvPlayerAudio {
|
|
uint16_t channels;
|
|
uint16_t unknown;
|
|
uint32_t sample_rate;
|
|
uint32_t size;
|
|
char language[4];
|
|
};
|
|
|
|
struct SceAvPlayerVideo {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
float aspect_ratio;
|
|
char language[4];
|
|
};
|
|
|
|
struct SceAvPlayerTextPosition {
|
|
uint16_t top;
|
|
uint16_t left;
|
|
uint16_t bottom;
|
|
uint16_t right;
|
|
};
|
|
|
|
struct SceAvPlayerTimedText {
|
|
char language[4];
|
|
uint16_t text_size;
|
|
uint16_t font_size;
|
|
SceAvPlayerTextPosition position;
|
|
};
|
|
|
|
union SceAvPlayerStreamDetails {
|
|
SceAvPlayerAudio audio;
|
|
SceAvPlayerVideo video;
|
|
SceAvPlayerTimedText text;
|
|
};
|
|
|
|
struct SceAvPlayerFrameInfo {
|
|
Ptr<uint8_t> data;
|
|
uint32_t unknown;
|
|
uint64_t timestamp;
|
|
SceAvPlayerStreamDetails stream_details;
|
|
};
|
|
|
|
enum class MediaType {
|
|
VIDEO,
|
|
AUDIO,
|
|
};
|
|
|
|
struct SceAvPlayerStreamInfo {
|
|
MediaType stream_type;
|
|
uint32_t unknown;
|
|
SceAvPlayerStreamDetails stream_details;
|
|
};
|
|
|
|
enum SceAvPlayerErrorCode : uint32_t {
|
|
SCE_AVPLAYER_ERROR_ILLEGAL_ADDR = 0x806a0001,
|
|
SCE_AVPLAYER_ERROR_INVALID_ARGUMENT = 0x806a0002,
|
|
SCE_AVPLAYER_ERROR_NOT_ENOUGH_MEMORY = 0x806a0003,
|
|
SCE_AVPLAYER_ERROR_INVALID_EVENT = 0x806a0004,
|
|
SCE_AVPLAYER_WAR_FILE_NONINTERLEAVED = 0x806a00a0,
|
|
SCE_AVPLAYER_WAR_LOOPING_BACK = 0x806a00a1,
|
|
SCE_AVPLAYER_WAR_JUMP_COMPLETE = 0x806a00a3
|
|
};
|
|
|
|
enum SceAvPlayerState {
|
|
SCE_AVPLAYER_STATE_UNKNOWN = 0,
|
|
SCE_AVPLAYER_STATE_STOP = 1,
|
|
SCE_AVPLAYER_STATE_READY = 2,
|
|
SCE_AVPLAYER_STATE_PLAY = 3,
|
|
SCE_AVPLAYER_STATE_PAUSE = 4,
|
|
SCE_AVPLAYER_STATE_BUFFERING = 5,
|
|
SCE_AVPLAYER_TIMED_TEXT_DELIVERY = 16,
|
|
SCE_AVPLAYER_WARNING_ID = 32,
|
|
SCE_AVPLAYER_ENCRYPTION = 48
|
|
};
|
|
|
|
static inline uint64_t current_time() {
|
|
return std::chrono::duration_cast<std::chrono::microseconds>(
|
|
std::chrono::high_resolution_clock::now().time_since_epoch())
|
|
.count();
|
|
}
|
|
|
|
static Ptr<uint8_t> get_buffer(const PlayerPtr &player, MediaType media_type,
|
|
MemState &mem, uint32_t size, bool new_frame = true) {
|
|
uint32_t &buffer_size = media_type == MediaType::VIDEO ? player->video_buffer_size : player->audio_buffer_size;
|
|
uint32_t &ring_index = media_type == MediaType::VIDEO ? player->video_buffer_ring_index : player->audio_buffer_ring_index;
|
|
auto &buffers = media_type == MediaType::VIDEO ? player->video_buffer : player->audio_buffer;
|
|
|
|
if (buffer_size < size) {
|
|
buffer_size = size;
|
|
for (uint32_t a = 0; a < PlayerInfoState::RING_BUFFER_COUNT; a++) {
|
|
if (buffers[a])
|
|
free(mem, buffers[a]);
|
|
std::string alloc_name = fmt::format("AvPlayer {} Media Ring {}",
|
|
media_type == MediaType::VIDEO ? "Video" : "Audio", a);
|
|
|
|
buffers[a] = alloc(mem, size, alloc_name.c_str());
|
|
}
|
|
}
|
|
|
|
if (new_frame)
|
|
ring_index++;
|
|
Ptr<uint8_t> buffer = buffers[ring_index % PlayerInfoState::RING_BUFFER_COUNT];
|
|
return buffer;
|
|
}
|
|
|
|
void run_event_callback(EmuEnvState &emuenv, const ThreadStatePtr &thread, const PlayerPtr &player_info, uint32_t event_id, uint32_t source_id, Ptr<void> event_data) {
|
|
if (player_info->event_manager.event_callback) {
|
|
thread->run_callback(player_info->event_manager.event_callback.address(), { player_info->event_manager.user_data, event_id, source_id, event_data.address() });
|
|
}
|
|
}
|
|
|
|
EXPORT(int32_t, sceAvPlayerAddSource, SceUID player_handle, Ptr<const char> path) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
|
|
if (!player_info) {
|
|
return RET_ERROR(SCE_AVPLAYER_ERROR_INVALID_ARGUMENT);
|
|
}
|
|
|
|
const auto thread = lock_and_find(thread_id, emuenv.kernel.threads, emuenv.kernel.mutex);
|
|
|
|
auto file_path = expand_path(emuenv.io, path.get(emuenv.mem), emuenv.pref_path);
|
|
if (!fs::exists(file_path) && player_info->file_manager.open_file && player_info->file_manager.close_file && player_info->file_manager.read_file && player_info->file_manager.file_size) {
|
|
fs::create_directories(emuenv.cache_path);
|
|
|
|
// Create temp media file
|
|
const auto temp_file_path = emuenv.cache_path / "temp_vita_media.mp4";
|
|
fs::ofstream temp_file(temp_file_path, std::ios::out | std::ios::binary);
|
|
|
|
const Address buf = alloc(emuenv.mem, KiB(512), "AvPlayer buffer");
|
|
const auto buf_ptr = Ptr<char>(buf).get(emuenv.mem);
|
|
thread->run_callback(player_info->file_manager.open_file.address(), { player_info->file_manager.user_data, path.address() });
|
|
// TODO: support file_size > 4GB (callback function returns uint64_t, but I dont know how to get high dword of uint64_t)
|
|
const uint32_t file_size = thread->run_callback(player_info->file_manager.file_size.address(), { player_info->file_manager.user_data });
|
|
auto remaining = file_size;
|
|
uint32_t offset = 0;
|
|
while (remaining) {
|
|
const auto buf_size = std::min((uint32_t)KiB(512), remaining);
|
|
// zero in 5 parameter means high dword of uint64_t parameter. see previous todo
|
|
thread->run_callback(player_info->file_manager.read_file.address(), { player_info->file_manager.user_data, buf, offset, 0, buf_size });
|
|
temp_file.write(buf_ptr, buf_size);
|
|
offset += buf_size;
|
|
remaining -= buf_size;
|
|
}
|
|
free(emuenv.mem, buf);
|
|
temp_file.close();
|
|
thread->run_callback(player_info->file_manager.close_file.address(), { player_info->file_manager.user_data });
|
|
if (fs::file_size(temp_file_path) != file_size) {
|
|
LOG_ERROR("File is corrupted or incomplete: {}", temp_file_path);
|
|
return -1;
|
|
}
|
|
file_path = temp_file_path;
|
|
}
|
|
|
|
player_info->player.queue(file_path.string());
|
|
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_BUFFERING, 0, Ptr<void>(0)); // may be important for sound
|
|
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_READY, 0, Ptr<void>(0));
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerClose, SceUID player_handle) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
const auto thread = emuenv.kernel.get_thread(thread_id);
|
|
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_STOP, 0, Ptr<void>(0));
|
|
std::lock_guard<std::mutex> lock(state->mutex);
|
|
state->players.erase(player_handle);
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(uint64_t, sceAvPlayerCurrentTime, SceUID player_handle) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
|
|
return player_info->player.last_timestamp;
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerDisableStream) {
|
|
return UNIMPLEMENTED();
|
|
}
|
|
|
|
EXPORT(int32_t, sceAvPlayerStreamCount, SceUID player_handle) {
|
|
STUBBED("ALWAYS RETURN 2 (VIDEO AND AUDIO)");
|
|
return 2;
|
|
}
|
|
|
|
EXPORT(int32_t, sceAvPlayerEnableStream, SceUID player_handle, uint32_t stream_no) {
|
|
if (player_handle == 0) {
|
|
return SCE_AVPLAYER_ERROR_ILLEGAL_ADDR;
|
|
}
|
|
if (stream_no > (uint32_t)(CALL_EXPORT(sceAvPlayerStreamCount, player_handle))) {
|
|
return SCE_AVPLAYER_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
return UNIMPLEMENTED();
|
|
}
|
|
|
|
EXPORT(bool, sceAvPlayerGetAudioData, SceUID player_handle, SceAvPlayerFrameInfo *frame_info) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
if (!player_info) {
|
|
return false;
|
|
}
|
|
Ptr<uint8_t> buffer;
|
|
|
|
if (player_info->paused) {
|
|
if (REJECT_DATA_ON_PAUSE) {
|
|
return false;
|
|
} else {
|
|
// This is probably incorrect and will make weird noises :P
|
|
buffer = get_buffer(player_info, MediaType::AUDIO, emuenv.mem,
|
|
player_info->player.last_sample_count * sizeof(int16_t) * player_info->player.last_channels, true);
|
|
}
|
|
} else {
|
|
std::vector<int16_t> data = player_info->player.receive_audio();
|
|
|
|
if (data.empty())
|
|
return false;
|
|
|
|
buffer = get_buffer(player_info, MediaType::AUDIO, emuenv.mem, (uint32_t)data.size() * sizeof(int16_t), false);
|
|
std::memcpy(buffer.get(emuenv.mem), data.data(), data.size() * sizeof(int16_t));
|
|
}
|
|
|
|
frame_info->timestamp = player_info->player.last_timestamp;
|
|
frame_info->stream_details.audio.channels = player_info->player.last_channels;
|
|
frame_info->stream_details.audio.sample_rate = player_info->player.last_sample_rate;
|
|
frame_info->stream_details.audio.size = player_info->player.last_channels * player_info->player.last_sample_count * sizeof(int16_t);
|
|
frame_info->data = buffer;
|
|
|
|
strcpy(frame_info->stream_details.audio.language, "ENG");
|
|
return true;
|
|
}
|
|
|
|
EXPORT(uint32_t, sceAvPlayerGetStreamInfo, SceUID player_handle, SceUInt32 stream_no, SceAvPlayerStreamInfo *stream_info) {
|
|
if (!stream_info) {
|
|
return SCE_AVPLAYER_ERROR_ILLEGAL_ADDR;
|
|
}
|
|
if (player_handle == 0) {
|
|
return SCE_AVPLAYER_ERROR_ILLEGAL_ADDR;
|
|
}
|
|
STUBBED("ALWAYS SUSPECTS 2 STREAMS: VIDEO AND AUDIO");
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
if (stream_no == 0) { // suspect always two streams: audio and video //first is video
|
|
DecoderSize size = player_info->player.get_size();
|
|
stream_info->stream_type = MediaType::VIDEO;
|
|
stream_info->stream_details.video.width = size.width;
|
|
stream_info->stream_details.video.height = size.height;
|
|
stream_info->stream_details.video.aspect_ratio = static_cast<float>(size.width) / static_cast<float>(size.height);
|
|
strcpy(stream_info->stream_details.video.language, "ENG");
|
|
} else if (stream_no == 1) { // audio
|
|
player_info->player.receive_audio(); // TODO: Get audio info without skipping data frames
|
|
stream_info->stream_type = MediaType::AUDIO;
|
|
stream_info->stream_details.audio.channels = player_info->player.last_channels;
|
|
stream_info->stream_details.audio.sample_rate = player_info->player.last_sample_rate;
|
|
stream_info->stream_details.audio.size = player_info->player.last_channels * player_info->player.last_sample_count * sizeof(int16_t);
|
|
strcpy(stream_info->stream_details.audio.language, "ENG");
|
|
} else {
|
|
return SCE_AVPLAYER_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(bool, sceAvPlayerGetVideoData, SceUID player_handle, SceAvPlayerFrameInfo *frame_info) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
if (!player_info) {
|
|
return false;
|
|
}
|
|
|
|
Ptr<uint8_t> buffer;
|
|
|
|
DecoderSize size = player_info->player.get_size();
|
|
|
|
uint64_t framerate = player_info->player.get_framerate_microseconds();
|
|
|
|
// needs new frame
|
|
if (player_info->last_frame_time + framerate < current_time()) {
|
|
if (CATCHUP_VIDEO_PLAYBACK)
|
|
player_info->last_frame_time += framerate;
|
|
else
|
|
player_info->last_frame_time = current_time();
|
|
|
|
if (player_info->paused) {
|
|
if (REJECT_DATA_ON_PAUSE)
|
|
return false;
|
|
else
|
|
buffer = get_buffer(player_info, MediaType::VIDEO, emuenv.mem, H264DecoderState::buffer_size(size), false);
|
|
} else {
|
|
buffer = get_buffer(player_info, MediaType::VIDEO, emuenv.mem, H264DecoderState::buffer_size(size), true);
|
|
|
|
std::vector<uint8_t> data = player_info->player.receive_video();
|
|
std::memcpy(buffer.get(emuenv.mem), data.data(), data.size());
|
|
}
|
|
} else {
|
|
buffer = get_buffer(player_info, MediaType::VIDEO, emuenv.mem, H264DecoderState::buffer_size(size), false);
|
|
}
|
|
// TODO: catch eof error and call
|
|
// uint32_t buf = SCE_AVPLAYER_ERROR_MAYBE_EOF;
|
|
// run_event_callback(emuenv, thread_id, player_info, SCE_AVPLAYER_STATE_ERROR, 0, &buf);
|
|
|
|
frame_info->timestamp = player_info->player.last_timestamp;
|
|
frame_info->stream_details.video.width = size.width;
|
|
frame_info->stream_details.video.height = size.height;
|
|
frame_info->stream_details.video.aspect_ratio = static_cast<float>(size.width) / static_cast<float>(size.height);
|
|
strcpy(frame_info->stream_details.video.language, "ENG");
|
|
frame_info->data = buffer;
|
|
return true;
|
|
}
|
|
|
|
EXPORT(bool, sceAvPlayerGetVideoDataEx, SceUID player_handle, SceAvPlayerFrameInfo *frame_info) {
|
|
STUBBED("Use GetVideoData");
|
|
return CALL_EXPORT(sceAvPlayerGetVideoData, player_handle, frame_info);
|
|
}
|
|
|
|
EXPORT(SceUID, sceAvPlayerInit, SceAvPlayerInfo *info) {
|
|
emuenv.kernel.obj_store.create<AvPlayerState>();
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
SceUID player_handle = emuenv.kernel.get_next_uid();
|
|
PlayerPtr player = std::make_shared<PlayerInfoState>();
|
|
state->players[player_handle] = player;
|
|
|
|
player->last_frame_time = current_time();
|
|
player->memory_allocator = info->memory_allocator;
|
|
player->file_manager = info->file_manager;
|
|
player->event_manager = info->event_manager;
|
|
|
|
// Result is defined as a void *, but I just call it SceUID because it is easier to deal with. Same size.
|
|
return player_handle;
|
|
}
|
|
|
|
EXPORT(bool, sceAvPlayerIsActive, SceUID player_handle) {
|
|
if (player_handle == 0) {
|
|
return false;
|
|
}
|
|
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
|
|
return !player_info->player.video_playing.empty();
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerJumpToTime) {
|
|
return UNIMPLEMENTED();
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerPause, SceUID player_handle) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
player_info->paused = true;
|
|
const auto thread = emuenv.kernel.get_thread(thread_id);
|
|
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_PAUSE, 0, Ptr<void>(0));
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerPostInit) {
|
|
return UNIMPLEMENTED();
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerResume, SceUID player_handle) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
if (!player_info->paused) {
|
|
const auto thread = emuenv.kernel.get_thread(thread_id);
|
|
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_PLAY, 0, Ptr<void>(0));
|
|
}
|
|
player_info->paused = false;
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerSetLooping, SceUID player_handle, bool do_loop) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
player_info->do_loop = do_loop;
|
|
|
|
return STUBBED("LOOPING NOT IMPLEMENTED");
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerSetTrickSpeed) {
|
|
return UNIMPLEMENTED();
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerStart, SceUID player_handle) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, state->mutex);
|
|
if (!player_info->player.videos_queue.empty()) {
|
|
player_info->player.pop_video();
|
|
}
|
|
const auto thread = emuenv.kernel.get_thread(thread_id);
|
|
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_PLAY, 0, Ptr<void>(0));
|
|
return 0;
|
|
}
|
|
|
|
EXPORT(int, sceAvPlayerStop, SceUID player_handle) {
|
|
const auto state = emuenv.kernel.obj_store.get<AvPlayerState>();
|
|
const PlayerPtr &player_info = lock_and_find(player_handle, state->players, emuenv.kernel.mutex);
|
|
player_info->player.free_video();
|
|
const auto thread = emuenv.kernel.get_thread(thread_id);
|
|
run_event_callback(emuenv, thread, player_info, SCE_AVPLAYER_STATE_STOP, 0, Ptr<void>(0));
|
|
return 0;
|
|
}
|