This commit is contained in:
2026-03-03 08:13:16 +03:00
commit 85685e7e17
19 changed files with 2887 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
#ifndef ECLIPT_CONFIG_H
#define ECLIPT_CONFIG_H
#include "Frame.h"
#include <string>
#include <vector>
#include <cstdint>
#include <functional>
namespace eclipt {
struct DecoderConfig {
bool use_hw_acceleration = true;
std::string hw_device = "auto";
int thread_count = 0;
bool enable_frame_skiping = false;
int max_ref_frames = 2;
bool low_latency = false;
};
struct OutputConfig {
PixelFormat preferred_format = PixelFormat::YUV420;
bool enable_frame_interpolation = false;
int target_fps = 0;
int frame_pool_size = 8;
bool vsync = true;
};
struct StreamConfig {
int video_stream_index = -1;
int audio_stream_index = -1;
int subtitle_stream_index = -1;
std::string preferred_language = "eng";
bool strict_stream_selection = false;
};
struct PlayerConfig {
DecoderConfig decoder;
OutputConfig output;
StreamConfig stream;
int buffer_size_ms = 1000;
bool prebuffer = true;
};
enum class LogLevel {
Debug,
Info,
Warning,
Error
};
enum class SeekDirection {
Forward,
Backward,
Absolute
};
using LogCallback = std::function<void(LogLevel, const char*)>;
}
#endif

View File

@@ -0,0 +1,156 @@
#ifndef ECLIPT_DECODER_H
#define ECLIPT_DECODER_H
#include "Frame.h"
#include "Demuxer.h"
#include "Config.h"
#include <string>
#include <vector>
#include <memory>
#include <functional>
#include <atomic>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavcodec/avcodec.h>
#include <libavutil/hwcontext.h>
#ifdef __cplusplus
}
#endif
namespace eclipt {
enum class DecoderType {
Software,
Hardware,
Auto
};
enum class HardwareAPI {
None,
VAAPI,
VDPAU,
CUDA,
D3D11VA,
VideoToolbox,
MediaCodec,
MMAL,
OMX
};
struct DecoderCapabilities {
bool supports_hw_decode = false;
bool supports_hw_encode = false;
std::vector<HardwareAPI> supported_apis;
std::vector<std::string> supported_codecs;
int max_resolution_width = 0;
int max_resolution_height = 0;
bool supports_10bit = false;
bool supports_hdr = false;
};
struct DecodeStats {
int decoded_frames = 0;
int dropped_frames = 0;
int corrupted_frames = 0;
int64_t decode_time_us = 0;
double fps = 0.0;
bool is_hardware = false;
HardwareAPI hw_api = HardwareAPI::None;
};
class IDecoder {
public:
virtual ~IDecoder() = default;
virtual bool open(const StreamMetadata& stream, const DecoderConfig& config) = 0;
virtual void close() = 0;
virtual bool isOpen() const = 0;
virtual bool sendPacket(const Packet& packet) = 0;
virtual bool receiveFrame(VideoFrame& frame) = 0;
virtual bool flush() = 0;
virtual DecoderType getDecoderType() const = 0;
virtual HardwareAPI getHardwareAPI() const = 0;
virtual const DecodeStats& getStats() const = 0;
virtual void setHardwareAPI(HardwareAPI api) = 0;
virtual bool supportsFormat(const std::string& codec) const = 0;
virtual std::string getLastError() const = 0;
};
class FFmpegDecoder : public IDecoder {
public:
FFmpegDecoder();
~FFmpegDecoder() override;
bool open(const StreamMetadata& stream, const DecoderConfig& config) override;
void close() override;
bool isOpen() const override;
bool sendPacket(const Packet& packet) override;
bool receiveFrame(VideoFrame& frame) override;
bool flush() override;
DecoderType getDecoderType() const override;
HardwareAPI getHardwareAPI() const override;
const DecodeStats& getStats() const override;
void setHardwareAPI(HardwareAPI api) override;
bool supportsFormat(const std::string& codec) const override;
std::string getLastError() const override;
static DecoderCapabilities getCapabilities();
static std::vector<HardwareAPI> getAvailableHardwareAPIs();
static HardwareAPI probeBestHardwareAPI(const std::string& codec);
void* getHardwareDeviceContext() const;
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
class FramePool : public IFramePool {
public:
FramePool(size_t max_size, PixelFormat format, uint32_t width, uint32_t height);
~FramePool() override;
VideoFrame acquire() override;
void release(VideoFrame&& frame) override;
void clear() override;
size_t available() const override;
void setFormat(PixelFormat format);
void setDimensions(uint32_t width, uint32_t height);
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
class DecoderPool {
public:
DecoderPool();
~DecoderPool();
void setMaxDecoders(size_t count);
size_t getMaxDecoders() const;
std::shared_ptr<FFmpegDecoder> acquire(const StreamMetadata& stream, const DecoderConfig& config);
void release(std::shared_ptr<FFmpegDecoder> decoder);
void clear();
size_t activeCount() const;
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
}
#endif

View File

@@ -0,0 +1,149 @@
#ifndef ECLIPT_DEMUXER_H
#define ECLIPT_DEMUXER_H
#include "Frame.h"
#include "Config.h"
#include <string>
#include <vector>
#include <memory>
#include <functional>
#ifdef __cplusplus
extern "C" {
#endif
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#ifdef __cplusplus
}
#endif
namespace eclipt {
enum class MediaType {
Video,
Audio,
Subtitle,
Data,
Attachment
};
struct StreamMetadata {
int stream_index = -1;
MediaType media_type = MediaType::Video;
std::string codec_name;
std::string codec_long_name;
std::string language;
std::string title;
int profile = 0;
int level = 0;
int width = 0;
int height = 0;
int64_t bitrate = 0;
int sample_rate = 0;
int channels = 0;
std::string channel_layout;
int fps_num = 0;
int fps_den = 1;
int64_t duration = 0;
bool is_default = false;
bool is_forced = false;
};
struct Packet {
std::vector<uint8_t> data;
int stream_index = -1;
int64_t pts = AV_NOPTS_VALUE;
int64_t dts = AV_NOPTS_VALUE;
int64_t duration = 0;
int flags = 0;
MediaType media_type = MediaType::Video;
Packet() = default;
bool isKeyframe() const { return flags & AV_PKT_FLAG_KEY; }
bool isCorrupt() const { return flags & AV_PKT_FLAG_CORRUPT; }
void clear() {
data.clear();
stream_index = -1;
pts = AV_NOPTS_VALUE;
dts = AV_NOPTS_VALUE;
duration = 0;
flags = 0;
}
};
class IDemuxer {
public:
virtual ~IDemuxer() = default;
virtual bool open(const std::string& url, const std::string& mime_type = "") = 0;
virtual void close() = 0;
virtual bool isOpen() const = 0;
virtual bool seek(int64_t timestamp_ms, eclipt::SeekDirection dir = eclipt::SeekDirection::Absolute) = 0;
virtual bool seekToKeyframe(int64_t timestamp_ms) = 0;
virtual bool readPacket(Packet& packet) = 0;
virtual int getPacketCount() const = 0;
virtual std::vector<StreamMetadata> getStreams() const = 0;
virtual const StreamMetadata* getStream(int index) const = 0;
virtual const StreamMetadata* getBestVideoStream() const = 0;
virtual const StreamMetadata* getBestAudioStream() const = 0;
virtual const StreamMetadata* getBestSubtitleStream() const = 0;
virtual int64_t getDuration() const = 0;
virtual int64_t getStartTime() const = 0;
virtual int64_t getBitrate() const = 0;
virtual std::string getUrl() const = 0;
virtual std::string getFormatName() const = 0;
virtual void setPreferredStream(int media_type, int stream_index) = 0;
using ReadCallback = std::function<size_t(uint8_t*, size_t)>;
virtual void setReadCallback(ReadCallback callback) = 0;
};
class FFmpegDemuxer : public IDemuxer {
public:
FFmpegDemuxer();
~FFmpegDemuxer() override;
bool open(const std::string& url, const std::string& mime_type = "") override;
void close() override;
bool isOpen() const override;
bool seek(int64_t timestamp_ms, eclipt::SeekDirection dir = eclipt::SeekDirection::Absolute) override;
bool seekToKeyframe(int64_t timestamp_ms) override;
bool readPacket(Packet& packet) override;
int getPacketCount() const override;
std::vector<StreamMetadata> getStreams() const override;
const StreamMetadata* getStream(int index) const override;
const StreamMetadata* getBestVideoStream() const override;
const StreamMetadata* getBestAudioStream() const override;
const StreamMetadata* getBestSubtitleStream() const override;
int64_t getDuration() const override;
int64_t getStartTime() const override;
int64_t getBitrate() const override;
std::string getUrl() const override;
std::string getFormatName() const override;
void setPreferredStream(int media_type, int stream_index) override;
void setReadCallback(ReadCallback callback) override;
void* getAVFormatContext() const;
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
}
#endif

View File

@@ -0,0 +1,104 @@
#ifndef ECLIPT_EPG_H
#define ECLIPT_EPG_H
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
#include <map>
#include <functional>
namespace eclipt {
struct EPGEvent {
int64_t id = 0;
std::string title;
std::string description;
int64_t start_time = 0;
int64_t end_time = 0;
int64_t duration = 0;
std::string channel_id;
std::string category;
std::string icon_url;
bool is_current = false;
bool is_future = false;
bool is_past = false;
};
struct EPGChannel {
std::string id;
std::string name;
std::string icon_url;
std::string group;
std::vector<EPGEvent> events;
const EPGEvent* getCurrentEvent() const;
std::vector<const EPGEvent*> getEventsInRange(int64_t start, int64_t end) const;
};
struct EPGData {
std::string source;
int64_t fetched_time = 0;
int64_t valid_from = 0;
int64_t valid_to = 0;
std::map<std::string, EPGChannel> channels;
bool loadFromFile(const std::string& path);
bool loadFromXml(const std::string& xml_content);
bool loadFromUrl(const std::string& url);
bool merge(const EPGData& other);
const EPGChannel* findChannel(const std::string& id) const;
std::vector<const EPGChannel*> findChannels(const std::string& group) const;
};
class EPGParser {
public:
virtual ~EPGParser() = default;
virtual bool parse(const std::string& content, EPGData& epg) = 0;
virtual std::string getLastError() const = 0;
};
class XmltvParser : public EPGParser {
public:
bool parse(const std::string& content, EPGData& epg) override;
std::string getLastError() const override { return last_error_; }
private:
std::string last_error_;
bool parseChannel(const std::string& xml, EPGChannel& channel);
bool parseProgramme(const std::string& xml, EPGEvent& event);
int64_t parseXmltvTime(const std::string& str);
};
class EPGFetcher {
public:
EPGFetcher();
~EPGFetcher();
void setBaseUrl(const std::string& url);
void setAuth(const std::string& username, const std::string& password);
bool fetch(EPGData& epg, const std::string& channel_id = "");
bool fetchAll(EPGData& epg);
void cancel();
bool isCancelled() const { return cancelled_; }
using ProgressCallback = std::function<void(int percent, const char* status)>;
void setProgressCallback(ProgressCallback callback);
private:
std::string base_url_;
std::string username_;
std::string password_;
bool cancelled_ = false;
ProgressCallback progress_callback_;
std::string buildUrl(const std::string& channel_id);
bool authenticate();
};
}
#endif

View File

@@ -0,0 +1,102 @@
#ifndef ECLIPT_PLAYER_H
#define ECLIPT_PLAYER_H
#include "Config.h"
#include "Frame.h"
#include "Playlist.h"
#include "EPG.h"
#include "Decoder.h"
#include <memory>
#include <atomic>
#include <mutex>
#include <condition_variable>
namespace eclipt {
enum class PlayerState {
Stopped,
Opening,
Buffering,
Playing,
Paused,
Error
};
struct PlayerStats {
int64_t current_pts = 0;
int64_t buffer_duration_ms = 0;
int dropped_frames = 0;
int decoded_frames = 0;
double fps = 0.0;
int network_bitrate = 0;
bool is_hardware_decoding = false;
};
class IPlatformHooks {
public:
virtual ~IPlatformHooks() = default;
virtual void* createHardwareContext() = 0;
virtual void destroyHardwareContext(void* ctx) = 0;
virtual bool uploadToTexture(const VideoFrame& frame, void* texture) = 0;
virtual bool supportsHardwareDecode(const char* codec) const = 0;
virtual std::string getPreferredDecoder() const = 0;
};
class EcliptPlayer {
public:
EcliptPlayer();
~EcliptPlayer();
void setConfig(const PlayerConfig& config);
PlayerConfig getConfig() const;
void setPlatformHooks(std::unique_ptr<IPlatformHooks> hooks);
void setLogCallback(LogCallback callback);
void setVideoCallback(FrameCallback callback);
void setAudioCallback(AudioCallback callback);
bool open(const std::string& url);
bool open(const std::string& url, const std::string& mime_type);
void close();
bool play();
bool pause();
bool stop();
bool seek(int64_t timestamp_ms, SeekDirection dir = SeekDirection::Absolute);
bool seekToProgram(unsigned int program_id);
bool seekToChannel(int channel_number);
PlayerState getState() const;
PlayerStats getStats() const;
float getVolume() const;
void setVolume(float volume);
bool setAudioTrack(int track_index);
bool setSubtitleTrack(int track_index);
std::vector<StreamInfo> getAudioTracks() const;
std::vector<StreamInfo> getSubtitleTracks() const;
bool isPlaying() const { return getState() == PlayerState::Playing; }
int64_t getDuration() const;
int64_t getCurrentPosition() const;
VideoFrame interpolate(const VideoFrame& a, const VideoFrame& b);
VideoFrame getDecodedFrame();
void setInterpolationEnabled(bool enabled);
bool isInterpolationEnabled() const;
static const char* getVersion();
static void setGlobalLogLevel(LogLevel level);
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
}
#endif

View File

@@ -0,0 +1,90 @@
#ifndef ECLIPT_FRAME_H
#define ECLIPT_FRAME_H
#include <cstdint>
#include <cstddef>
#include <memory>
#include <vector>
#include <functional>
namespace eclipt {
enum class PixelFormat {
YUV420,
RGB24,
NV12,
BGRA32
};
enum class FrameType {
Video,
Audio,
Subtitle
};
struct FrameBuffer {
uint8_t* data = nullptr;
size_t size = 0;
size_t stride = 0;
FrameBuffer() = default;
FrameBuffer(uint8_t* d, size_t s, size_t st = 0) : data(d), size(s), stride(st) {}
};
struct VideoFrame {
uint32_t width = 0;
uint32_t height = 0;
PixelFormat format = PixelFormat::YUV420;
int64_t pts = 0;
int64_t dts = 0;
int64_t duration = 0;
FrameBuffer planes[3];
bool is_interpolated = false;
float interpolation_factor = 0.0f;
VideoFrame() = default;
bool isValid() const { return width > 0 && height > 0 && planes[0].data != nullptr; }
size_t totalSize() const {
switch (format) {
case PixelFormat::YUV420:
return width * height * 3 / 2;
case PixelFormat::RGB24:
return width * height * 3;
case PixelFormat::NV12:
return width * height * 3 / 2;
case PixelFormat::BGRA32:
return width * height * 4;
default:
return 0;
}
}
};
struct AudioFrame {
int sample_rate = 0;
int channels = 0;
int format = 0;
int64_t pts = 0;
int64_t duration = 0;
FrameBuffer buffer;
size_t totalSize() const { return buffer.size; }
};
class IFramePool {
public:
virtual ~IFramePool() = default;
virtual VideoFrame acquire() = 0;
virtual void release(VideoFrame&& frame) = 0;
virtual void clear() = 0;
virtual size_t available() const = 0;
};
using FrameCallback = std::function<void(VideoFrame&&)>;
using AudioCallback = std::function<void(AudioFrame&&)>;
}
#endif

View File

@@ -0,0 +1,78 @@
#ifndef ECLIPT_PLAYLIST_H
#define ECLIPT_PLAYLIST_H
#include <string>
#include <vector>
#include <cstdint>
#include <memory>
#include <functional>
namespace eclipt {
enum class PlaylistType {
M3U,
M3U8,
PLS,
EXT
};
struct StreamInfo {
int index = -1;
std::string name;
std::string language;
std::string codec;
int bandwidth = 0;
std::string url;
std::string group;
bool is_default = false;
bool is_selected = false;
};
struct PlaylistItem {
std::string name;
std::string url;
std::string tvg_name;
std::string tvg_id;
std::string group;
int tvg_logo = 0;
int64_t duration = -1;
bool is_live = false;
std::vector<StreamInfo> streams;
};
struct Playlist {
std::string name;
PlaylistType type = PlaylistType::M3U;
std::vector<PlaylistItem> items;
std::string base_url;
Playlist() = default;
bool loadFromFile(const std::string& path);
bool loadFromString(const std::string& content);
bool loadFromUrl(const std::string& url);
std::vector<PlaylistItem> getLiveStreams() const;
std::vector<PlaylistItem> getByGroup(const std::string& group) const;
PlaylistItem* findById(const std::string& id);
};
class IPlaylistParser {
public:
virtual ~IPlaylistParser() = default;
virtual bool parse(const std::string& content, Playlist& playlist) = 0;
virtual std::string getLastError() const = 0;
};
class M3UParser : public IPlaylistParser {
public:
bool parse(const std::string& content, Playlist& playlist) override;
std::string getLastError() const override { return last_error_; }
private:
std::string last_error_;
bool parseExtInf(const std::string& line, PlaylistItem& item);
bool parseAttribute(const std::string& attr, std::string& key, std::string& value);
};
}
#endif

View File

@@ -0,0 +1,145 @@
#ifndef ECLIPT_SUBTITLE_H
#define ECLIPT_SUBTITLE_H
#include "Frame.h"
#include <string>
#include <vector>
#include <memory>
#include <cstdint>
namespace eclipt {
enum class SubtitleType {
SRT,
VTT,
ASS,
SSA,
DVBSub,
DVB_Teletext,
EIA_608
};
struct SubtitleCue {
int64_t start_time = 0;
int64_t end_time = 0;
std::string text;
std::vector<std::string> lines;
int style_index = -1;
bool isValid() const { return !text.empty() && end_time > start_time; }
bool isActive(int64_t pts) const {
return pts >= start_time && pts < end_time;
}
};
struct SubtitleStyle {
std::string font_name;
int font_size = 24;
uint32_t primary_color = 0xFFFFFFFF;
uint32_t secondary_color = 0xFF000000;
uint32_t outline_color = 0xFF000000;
uint32_t back_color = 0x00000000;
int bold = 0;
int italic = 0;
int underline = 0;
int strikeout = 0;
double margin_l = 0;
double margin_r = 0;
double margin_v = 0;
int alignment = 2;
double scale_x = 1.0;
double scale_y = 1.0;
};
struct RenderedSubtitle {
VideoFrame frame;
int64_t pts = 0;
int64_t duration = 0;
bool has_ass_events = false;
RenderedSubtitle() = default;
bool isValid() const { return frame.isValid(); }
};
class ISubtitleRenderer {
public:
virtual ~ISubtitleRenderer() = default;
virtual bool initialize(int width, int height) = 0;
virtual void shutdown() = 0;
virtual bool loadSubtitles(const std::string& path) = 0;
virtual bool loadSubtitles(const uint8_t* data, size_t size, SubtitleType type) = 0;
virtual void clearSubtitles() = 0;
virtual void setStyle(const SubtitleStyle& style) = 0;
virtual SubtitleStyle getStyle() const = 0;
virtual RenderedSubtitle render(int64_t pts) = 0;
virtual const SubtitleCue* getCurrentCue(int64_t pts) const = 0;
virtual bool hasSubtitles() const = 0;
virtual size_t getCueCount() const = 0;
};
class SubtitleDecoder {
public:
SubtitleDecoder();
~SubtitleDecoder();
bool open(const std::string& url);
bool open(const uint8_t* data, size_t size, SubtitleType type);
void close();
bool isOpen() const { return is_open_; }
SubtitleType getType() const { return current_type_; }
std::vector<SubtitleCue> getCues() const { return cues_; }
const SubtitleCue* getCueAtTime(int64_t pts) const;
bool parse();
private:
bool is_open_ = false;
SubtitleType current_type_ = SubtitleType::SRT;
std::vector<SubtitleCue> cues_;
std::vector<uint8_t> data_;
bool parseSRT();
bool parseVTT();
bool parseASS();
int64_t parseTimestamp(const std::string& str);
std::vector<std::string> splitLines(const std::string& text);
};
class SubtitleRenderer : public ISubtitleRenderer {
public:
SubtitleRenderer();
~SubtitleRenderer() override;
bool initialize(int width, int height) override;
void shutdown() override;
bool loadSubtitles(const std::string& path) override;
bool loadSubtitles(const uint8_t* data, size_t size, SubtitleType type) override;
void clearSubtitles() override;
void setStyle(const SubtitleStyle& style) override;
SubtitleStyle getStyle() const override;
RenderedSubtitle render(int64_t pts) override;
const SubtitleCue* getCurrentCue(int64_t pts) const override;
bool hasSubtitles() const override;
size_t getCueCount() const override;
void setVideoParams(int width, int height, int fps_num, int fps_den);
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
}
#endif