* Copyright (c) 2023-2025 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define HST_LOG_TAG "FileSourcePlugin"
#include <sys/stat.h>
#include "common/log.h"
#include "file_source_plugin.h"
#include "plugin/plugin_time.h"
namespace {
constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, LOG_DOMAIN_SYSTEM_PLAYER, "HiStreamer" };
}
namespace OHOS {
namespace Media {
namespace Plugins {
namespace FileSource {
namespace {
size_t GetFileSize(const std::string& fileName)
{
size_t fileSize = 0;
if (!fileName.empty()) {
struct stat fileStatus {};
if (stat(fileName.c_str(), &fileStatus) == 0) {
fileSize = static_cast<size_t>(fileStatus.st_size);
}
}
return fileSize;
}
}
std::shared_ptr<SourcePlugin> FileSourcePluginCreator(const std::string& name)
{
return std::make_shared<FileSourcePlugin>(name);
}
Status FileSourceRegister(const std::shared_ptr<Register>& reg)
{
SourcePluginDef definition;
definition.name = "FileSource";
definition.description = "File source";
definition.rank = 100;
Capability capability;
capability.AppendFixedKey<std::vector<ProtocolType>>(Tag::MEDIA_PROTOCOL_TYPE, {ProtocolType::FILE});
definition.AddInCaps(capability);
auto func = [](const std::string& name) -> std::shared_ptr<SourcePlugin> {
return std::make_shared<FileSourcePlugin>(name);
};
definition.SetCreator(func);
return reg->AddPlugin(definition);
}
PLUGIN_DEFINITION(FileSource, LicenseType::APACHE_V2, FileSourceRegister, [] {});
void* FileSourceAllocator::Alloc(size_t size)
{
if (size == 0) {
MEDIA_LOG_E("Invalid zero size");
return nullptr;
}
return static_cast<void*>(new (std::nothrow) uint8_t[size]);
}
void FileSourceAllocator::Free(void* ptr)
{
if (ptr != nullptr) {
delete[](uint8_t*) ptr;
}
}
FileSourcePlugin::FileSourcePlugin(std::string name)
: SourcePlugin(std::move(name)), fp_(nullptr), fileSize_(0), seekable_(Seekable::SEEKABLE), position_(0)
{
MEDIA_LOG_D("IN");
}
FileSourcePlugin::~FileSourcePlugin()
{
MEDIA_LOG_D("IN");
if (fp_) {
std::fclose(fp_);
fp_ = nullptr;
}
}
Status FileSourcePlugin::Init()
{
MEDIA_LOG_D("IN");
mAllocator_ = std::make_shared<FileSourceAllocator>();
return Status::OK;
}
Status FileSourcePlugin::Deinit()
{
MEDIA_LOG_D("IN");
CloseFile();
return Status::OK;
}
Status FileSourcePlugin::Prepare()
{
MEDIA_LOG_D("IN");
return Status::OK;
}
Status FileSourcePlugin::Reset()
{
MEDIA_LOG_D("IN");
CloseFile();
return Status::OK;
}
Status FileSourcePlugin::GetParameter(std::shared_ptr<Meta> &meta)
{
MEDIA_LOG_D("IN");
return Status::ERROR_UNIMPLEMENTED;
}
Status FileSourcePlugin::SetParameter(const std::shared_ptr<Meta> &meta)
{
MEDIA_LOG_D("IN");
return Status::ERROR_UNIMPLEMENTED;
}
Status FileSourcePlugin::SetMediaDuration(int64_t mediaDuration)
{
MEDIA_LOG_D("IN");
durationUs_ = mediaDuration;
return Status::OK;
}
std::shared_ptr<Allocator> FileSourcePlugin::GetAllocator()
{
MEDIA_LOG_D("IN");
return mAllocator_;
}
Status FileSourcePlugin::SetCallback(const std::shared_ptr<Callback>& cb)
{
MEDIA_LOG_D("IN");
return Status::ERROR_UNIMPLEMENTED;
}
Status FileSourcePlugin::SetSource(std::shared_ptr<MediaSource> source)
{
MEDIA_LOG_D("IN");
auto err = ParseFileName(source->GetSourceUri());
if (err != Status::OK) {
MEDIA_LOG_E("Parse file name from uri fail, uri: " PUBLIC_LOG_S, source->GetSourceUri().c_str());
return err;
}
return OpenFile();
}
Status FileSourcePlugin::Read(std::shared_ptr<Buffer>& buffer, uint64_t offset, size_t expectedLen)
{
return Read(0, buffer, offset, expectedLen);
}
Status FileSourcePlugin::Read(int32_t streamId, std::shared_ptr<Buffer>& buffer, uint64_t offset, size_t expectedLen)
{
FALSE_RETURN_V_MSG_E(fp_ != nullptr, Status::ERROR_WRONG_STATE, "invalid fp");
(void)offset;
if (std::feof(fp_) || (fileSize_ == position_)) {
MEDIA_LOG_W("It is the end of file!");
return Status::END_OF_STREAM;
}
if (buffer == nullptr) {
buffer = std::make_shared<Buffer>();
}
std::shared_ptr<Memory> bufData;
int64_t start = GetCurrentMillisecond();
if (buffer->IsEmpty()) {
bufData = buffer->AllocMemory(GetAllocator(), expectedLen);
} else {
bufData = buffer->GetMemory();
}
if (bufData == nullptr) {
return Status::ERROR_AGAIN;
}
expectedLen = std::min(static_cast<size_t>(fileSize_ - position_), expectedLen);
expectedLen = std::min(bufData->GetCapacity(), expectedLen);
MEDIA_LOG_DD("buffer position " PUBLIC_LOG_U64 ", expectedLen " PUBLIC_LOG_ZU, position_, expectedLen);
auto bufDataAddr = bufData->GetWritableAddr(expectedLen);
if (bufDataAddr == nullptr) {
MEDIA_LOG_E("Read bufData GetWritableAddr fail");
return Status::ERROR_NO_MEMORY;
}
auto size = std::fread(bufDataAddr, sizeof(char), expectedLen, fp_);
bufData->UpdateDataSize(size);
position_ += bufData->GetSize();
totalDownLoadBytes_ += static_cast<int64_t>(size);
if (totalDownloadCount_ == 0) {
firstDownloadTimestamp_ = GetCurrentMillisecond();
firstDownloadTime_ = firstDownloadTimestamp_ - start;
}
totalDownloadCount_++;
int64_t end = GetCurrentMillisecond();
totalDownloadDuringTime_ += end - start;
MEDIA_LOG_DD("position_: " PUBLIC_LOG_U64 ", readSize: " PUBLIC_LOG_ZU, position_, bufData->GetSize());
return Status::OK;
}
Status FileSourcePlugin::GetSize(uint64_t& size)
{
MEDIA_LOG_DD("IN");
if (!fp_) {
MEDIA_LOG_E("Need call SetSource() to open file first");
return Status::ERROR_WRONG_STATE;
}
size = fileSize_;
MEDIA_LOG_DD("FileSize_: " PUBLIC_LOG_U64, size);
return Status::OK;
}
Seekable FileSourcePlugin::GetSeekable()
{
MEDIA_LOG_DD("IN");
return seekable_;
}
std::vector<SeekRange> FileSourcePlugin::GetSeekableRanges() const
{
if (seekable_ != Seekable::SEEKABLE || durationUs_ <= 0) {
return {};
}
int64_t durationHst = 0;
if (!Plugins::Us2HstTime(durationUs_, durationHst)) {
return {};
}
return {{0, durationHst}};
}
Status FileSourcePlugin::SeekTo(uint64_t offset)
{
if (!fp_ || (offset > fileSize_) || (position_ == offset)) {
MEDIA_LOG_E("Invalid operation");
return Status::ERROR_WRONG_STATE;
}
std::clearerr(fp_);
if (std::fseek(fp_, static_cast<long int>(offset), SEEK_SET) != 0) {
std::clearerr(fp_);
(void)std::fseek(fp_, static_cast<long int>(position_), SEEK_SET);
MEDIA_LOG_E("Seek to " PUBLIC_LOG_U64, offset);
return Status::ERROR_UNKNOWN;
}
position_ = offset;
if (std::feof(fp_)) {
MEDIA_LOG_I("It is the end of file!");
}
MEDIA_LOG_D("seek to position_: " PUBLIC_LOG_U64 " success", position_);
return Status::OK;
}
Status FileSourcePlugin::ParseFileName(const std::string& uri)
{
if (uri.empty()) {
MEDIA_LOG_E("uri is empty");
return Status::ERROR_INVALID_PARAMETER;
}
if (uri.find("file:/") != std::string::npos) {
if (uri.find('#') != std::string::npos) {
MEDIA_LOG_E("Invalid file uri format");
return Status::ERROR_INVALID_PARAMETER;
}
auto pos = uri.find("file:");
if (pos == std::string::npos) {
MEDIA_LOG_E("Invalid file uri format");
return Status::ERROR_INVALID_PARAMETER;
}
pos += 5;
if (uri.find("///", pos) != std::string::npos) {
pos += 2;
} else if (uri.find("//", pos) != std::string::npos) {
pos += 2;
pos = uri.find('/', pos);
if (pos == std::string::npos) {
MEDIA_LOG_E("Invalid file uri format");
return Status::ERROR_INVALID_PARAMETER;
}
pos++;
}
fileName_ = uri.substr(pos);
} else {
fileName_ = uri;
}
MEDIA_LOG_D("fileName_: " PUBLIC_LOG_S, fileName_.c_str());
return Status::OK;
}
Status FileSourcePlugin::CheckFileStat()
{
struct stat fileStat;
if (stat(fileName_.c_str(), &fileStat) < 0) {
MEDIA_LOG_E("Cannot get info from " PUBLIC_LOG_S, fileName_.c_str());
return Status::ERROR_NOT_EXISTED;
}
if (S_ISDIR(fileStat.st_mode)) {
MEDIA_LOG_E(PUBLIC_LOG_S " is directory", fileName_.c_str());
return Status::ERROR_UNSUPPORTED_FORMAT;
}
if (S_ISSOCK(fileStat.st_mode)) {
MEDIA_LOG_E(PUBLIC_LOG_S " is a socket", fileName_.c_str());
return Status::ERROR_UNSUPPORTED_FORMAT;
}
return Status::OK;
}
Status FileSourcePlugin::OpenFile()
{
MEDIA_LOG_D("IN");
auto ret = CheckFileStat();
if (ret != Status::OK) {
CloseFile();
return ret;
}
CloseFile();
fp_ = std::fopen(fileName_.c_str(), "rb");
if (fp_ == nullptr) {
MEDIA_LOG_E("Fail to load file from " PUBLIC_LOG_S, fileName_.c_str());
return Status::ERROR_UNKNOWN;
}
fileSize_ = GetFileSize(fileName_);
position_ = 0;
MEDIA_LOG_D("FileName_: " PUBLIC_LOG_S ", fileSize_: " PUBLIC_LOG_U64, fileName_.c_str(), fileSize_);
return Status::OK;
}
void FileSourcePlugin::CloseFile()
{
if (fp_) {
MEDIA_LOG_I("close file");
std::fclose(fp_);
fp_ = nullptr;
}
}
bool FileSourcePlugin::IsLocalFd()
{
return true;
}
Status FileSourcePlugin::GetDownloadInfo(DownloadInfo& downloadInfo)
{
downloadInfo.totalDownLoadBytes = totalDownLoadBytes_;
downloadInfo.totalLoadingTime = totalDownloadDuringTime_;
downloadInfo.loadingCount = totalDownloadCount_;
downloadInfo.firstDownloadTime = firstDownloadTime_;
downloadInfo.firstFrameDecapsulationTime = firstDownloadTimestamp_;
return Status::OK;
}
int64_t FileSourcePlugin::GetCurrentMillisecond()
{
auto duration = std::chrono::steady_clock::now().time_since_epoch();
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
}
}
}
}