diff --git a/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.cpp b/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.cpp
index 0d4ed635d..6b775a6d7 100644
--- a/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.cpp
+++ b/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.cpp
@@ -10,8 +10,9 @@
#include "../../../../../api_private.h"
#include
-#include
-#include
+#include
+#include
+#include
namespace api
{
@@ -25,6 +26,7 @@ namespace api
RegisterPost(R"((startHlsDump))", &StreamActionsController::OnPostStartHLSDump);
RegisterPost(R"((stopHlsDump))", &StreamActionsController::OnPostStopHLSDump);
RegisterPost(R"((sendEvent))", &StreamActionsController::OnPostSendEvent);
+ RegisterPost(R"((sendEvents))", &StreamActionsController::OnPostSendEvents);
RegisterPost(R"((concludeHlsLive))", &StreamActionsController::OnPostConcludeHlsLive);
}
@@ -218,6 +220,42 @@ namespace api
return {http::StatusCode::OK};
}
+ // POST /v1/vhosts//apps//streams/:injectHLSEvent
+ ApiResponse StreamActionsController::OnPostSendEvents(const std::shared_ptr &client, const Json::Value &request_body,
+ const std::shared_ptr &vhost,
+ const std::shared_ptr &app,
+ const std::shared_ptr &stream,
+ const std::vector> &output_streams)
+ {
+ if (request_body.isArray() == false || request_body.size() == 0)
+ {
+ throw http::HttpError(http::StatusCode::BadRequest, "events(array) is required");
+ }
+
+ MultipleStatus status_codes;
+ Json::Value response_value(Json::ValueType::arrayValue);
+ for (const auto &request_event : request_body)
+ {
+ try
+ {
+ OnPostSendEvent(client, request_event, vhost, app, stream, output_streams);
+ status_codes.AddStatusCode(http::StatusCode::OK);
+
+ Json::Value response;
+ response["statusCode"] = static_cast(http::StatusCode::OK);
+ response["message"] = StringFromStatusCode(http::StatusCode::OK);
+ response_value.append(response);
+ }
+ catch (const http::HttpError &error)
+ {
+ status_codes.AddStatusCode(error.GetStatusCode());
+ response_value.append(::serdes::JsonFromError(error));
+ }
+ }
+
+ return {status_codes, std::move(response_value)};
+ }
+
// POST /v1/vhosts//apps//streams/:injectHLSEvent
ApiResponse StreamActionsController::OnPostSendEvent(const std::shared_ptr &client, const Json::Value &request_body,
const std::shared_ptr &vhost,
@@ -226,6 +264,8 @@ namespace api
const std::vector> &output_streams)
{
// Validate request body
+
+ // ID3 Event
// {
// "eventFormat": "id3v2",
// "eventType": "video",
@@ -249,79 +289,48 @@ namespace api
// ]
// }
+ // Cue Event
+ // {
+ // "eventFormat": "cue",
+ // "events":[
+ // {
+ // "cueType": "out", // out | in
+ // "duration": 60500 // milliseconds, only available when cueType is out
+ // }
+ // ]
+ // }
+
if (request_body.isMember("eventFormat") == false || request_body["eventFormat"].isString() == false ||
- request_body.isMember("events") == false || request_body["events"].isArray() == false)
+ request_body.isMember("events") == false || request_body["events"].isArray() == false || request_body["events"].size() == 0)
{
throw http::HttpError(http::StatusCode::BadRequest, "eventFormat(string) and events(array) are required");
}
- // Now only support ID3v2 format
+ cmn::BitstreamFormat event_format = cmn::BitstreamFormat::Unknown;
ov::String event_format_string = request_body["eventFormat"].asString().c_str();
- if (event_format_string.UpperCaseString() != "ID3V2")
+ std::shared_ptr events_data = nullptr;
+
+ if (event_format_string.UpperCaseString() == "ID3V2")
{
- throw http::HttpError(http::StatusCode::BadRequest, "eventFormat is not supported: [%s]", event_format_string.CStr());
+ event_format = cmn::BitstreamFormat::ID3v2;
+ events_data = MakeID3Data(request_body["events"]);
}
-
- cmn::BitstreamFormat event_format = cmn::BitstreamFormat::ID3v2;
- bool urgent = false;
-
- if (request_body.isMember("urgent") == true && request_body["urgent"].isBool() == true)
+ else if (event_format_string.UpperCaseString() == "CUE")
{
- urgent = request_body["urgent"].asBool();
+ event_format = cmn::BitstreamFormat::CUE;
+ events_data = MakeCueData(request_body["events"]);
}
-
- auto events = request_body["events"];
- if (events.size() == 0)
+ else
{
- throw http::HttpError(http::StatusCode::BadRequest, "events is empty");
+ throw http::HttpError(http::StatusCode::BadRequest, "eventFormat is not supported: [%s]", event_format_string.CStr());
}
- // Make ID3v2 tags
- auto id3v2_event = std::make_shared();
- id3v2_event->SetVersion(4, 0);
- for (const auto &event : events)
+ if (events_data == nullptr)
{
- if (event.isMember("frameType") == false || event["frameType"].isString() == false)
- {
- throw http::HttpError(http::StatusCode::BadRequest, "frameType is required in events");
- }
-
- ov::String frame_type = event["frameType"].asString().c_str();
- ov::String info;
- ov::String data;
-
- if (event.isMember("info") == true && event["info"].isString() == true)
- {
- info = event["info"].asString().c_str();
- }
-
- if (event.isMember("data") == true && event["data"].isString() == true)
- {
- data = event["data"].asString().c_str();
- }
-
- std::shared_ptr frame;
- if (frame_type.UpperCaseString() == "TXXX")
- {
- frame = std::make_shared(info, data);
- }
- else if (frame_type.UpperCaseString().Get(0) == 'T')
- {
- frame = std::make_shared(frame_type, data);
- }
- else if (frame_type.UpperCaseString() == "PRIV")
- {
- frame = std::make_shared(info, data);
- }
- else
- {
- throw http::HttpError(http::StatusCode::BadRequest, "frameType is not supported: [%s]", frame_type.CStr());
- }
-
- id3v2_event->AddFrame(frame);
+ throw http::HttpError(http::StatusCode::BadRequest, "Could not make events data");
}
- // Event Type
+ // Event Type (Optional)
cmn::PacketType event_type = cmn::PacketType::EVENT;
if (request_body.isMember("eventType") == true)
{
@@ -349,7 +358,14 @@ namespace api
throw http::HttpError(http::StatusCode::BadRequest, "eventType is not supported: [%s]", event_type_string.CStr());
}
}
-
+
+ // Urgent (Optional)
+ bool urgent = false;
+ if (request_body.isMember("urgent") == true && request_body["urgent"].isBool() == true)
+ {
+ urgent = request_body["urgent"].asBool();
+ }
+
auto source_stream = GetSourceStream(stream);
if (source_stream == nullptr)
{
@@ -358,7 +374,7 @@ namespace api
vhost->GetName().CStr(), app->GetVHostAppName().GetAppName().CStr(), stream->GetName().CStr());
}
- if (source_stream->SendDataFrame(-1, event_format, event_type, id3v2_event->Serialize(), urgent) == false)
+ if (source_stream->SendDataFrame(-1, event_format, event_type, events_data, urgent) == false)
{
throw http::HttpError(http::StatusCode::InternalServerError,
"Internal Server Error - Could not inject event: [%s/%s/%s]",
@@ -473,5 +489,88 @@ namespace api
return application->GetStreamByName(stream->GetName());
}
- }
-}
\ No newline at end of file
+
+ std::shared_ptr StreamActionsController::MakeID3Data(const Json::Value &events)
+ {
+ // Make ID3v2 tags
+ auto id3v2_event = std::make_shared();
+ id3v2_event->SetVersion(4, 0);
+ for (const auto &event : events)
+ {
+ if (event.isMember("frameType") == false || event["frameType"].isString() == false)
+ {
+ throw http::HttpError(http::StatusCode::BadRequest, "frameType is required in events");
+ }
+
+ ov::String frame_type = event["frameType"].asString().c_str();
+ ov::String info;
+ ov::String data;
+
+ if (event.isMember("info") == true && event["info"].isString() == true)
+ {
+ info = event["info"].asString().c_str();
+ }
+
+ if (event.isMember("data") == true && event["data"].isString() == true)
+ {
+ data = event["data"].asString().c_str();
+ }
+
+ std::shared_ptr frame;
+ if (frame_type.UpperCaseString() == "TXXX")
+ {
+ frame = std::make_shared(info, data);
+ }
+ else if (frame_type.UpperCaseString().Get(0) == 'T')
+ {
+ frame = std::make_shared(frame_type, data);
+ }
+ else if (frame_type.UpperCaseString() == "PRIV")
+ {
+ frame = std::make_shared(info, data);
+ }
+ else
+ {
+ throw http::HttpError(http::StatusCode::BadRequest, "frameType is not supported: [%s]", frame_type.CStr());
+ }
+
+ id3v2_event->AddFrame(frame);
+ }
+
+ return id3v2_event->Serialize();
+ }
+
+ std::shared_ptr StreamActionsController::MakeCueData(const Json::Value &events)
+ {
+ if (events.size() == 0)
+ {
+ throw http::HttpError(http::StatusCode::BadRequest, "events must have at least one event");
+ }
+
+ // only first event is used
+ auto event = events[0];
+ if (event.isMember("cueType") == false || event["cueType"].isString() == false)
+ {
+ throw http::HttpError(http::StatusCode::BadRequest, "cueType is required in events");
+ }
+
+ ov::String cue_type = event["cueType"].asString().c_str();
+ CueEvent::CueType type = CueEvent::GetCueTypeByName(cue_type);
+ if (type == CueEvent::CueType::Unknown)
+ {
+ throw http::HttpError(http::StatusCode::BadRequest, "cueType is not supported: [%s]", cue_type.CStr());
+ }
+
+ // duration (optional)
+ uint32_t duration_msec = 0;
+ if (event.isMember("duration") == true && event["duration"].isUInt() == true)
+ {
+ duration_msec = event["duration"].asUInt();
+ }
+
+ auto cue_event = CueEvent::Create(type, duration_msec);
+
+ return cue_event->Serialize();
+ }
+ } // namespace v1
+} // namespace api
\ No newline at end of file
diff --git a/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.h b/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.h
index 1b665f58e..ff4979b04 100644
--- a/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.h
+++ b/src/projects/api_server/controllers/v1/vhosts/apps/streams/stream_actions_controller.h
@@ -46,6 +46,13 @@ namespace api
const std::shared_ptr &app,
const std::shared_ptr &stream,
const std::vector> &output_streams);
+
+ // POST /v1/vhosts//apps//streams/:sendEvent
+ ApiResponse OnPostSendEvents(const std::shared_ptr &client, const Json::Value &request_body,
+ const std::shared_ptr &vhost,
+ const std::shared_ptr &app,
+ const std::shared_ptr &stream,
+ const std::vector> &output_streams);
// POST /v1/vhosts//apps//streams/:concludeHlsLive
ApiResponse OnPostConcludeHlsLive(const std::shared_ptr &client, const Json::Value &request_body,
@@ -89,6 +96,10 @@ namespace api
return std::static_pointer_cast(stream);
}
+
+
+ std::shared_ptr MakeID3Data(const Json::Value &events); // ID3v2
+ std::shared_ptr MakeCueData(const Json::Value &events); // CUE
};
}
}
\ No newline at end of file
diff --git a/src/projects/base/mediarouter/media_type.h b/src/projects/base/mediarouter/media_type.h
index 1fc933935..977ecf55b 100644
--- a/src/projects/base/mediarouter/media_type.h
+++ b/src/projects/base/mediarouter/media_type.h
@@ -48,7 +48,9 @@ namespace cmn
MP3,
- OVEN_EVENT // OvenMediaEngine defined event
+ OVEN_EVENT, // OvenMediaEngine defined event
+
+ CUE
};
enum class PacketType : int8_t
diff --git a/src/projects/main/AMS.mk b/src/projects/main/AMS.mk
index 9c81e04bc..8ed5d5c9c 100644
--- a/src/projects/main/AMS.mk
+++ b/src/projects/main/AMS.mk
@@ -30,6 +30,7 @@ LOCAL_STATIC_LIBRARIES := \
rtp_rtcp \
sdp \
id3v2 \
+ cue_event \
segment_writer \
web_console \
mediarouter \
diff --git a/src/projects/modules/containers/bmff/bmff_packager.cpp b/src/projects/modules/containers/bmff/bmff_packager.cpp
index c86042599..f3d9ca89c 100644
--- a/src/projects/modules/containers/bmff/bmff_packager.cpp
+++ b/src/projects/modules/containers/bmff/bmff_packager.cpp
@@ -9,8 +9,8 @@
#include "bmff_packager.h"
#include "bmff_private.h"
-#include
-#include
+#include
+#include
namespace bmff
{
Packager::Packager(const std::shared_ptr &media_track, const std::shared_ptr &data_track, const CencProperty &cenc_property)
diff --git a/src/projects/modules/containers/bmff/fmp4_packager/fmp4_packager.cpp b/src/projects/modules/containers/bmff/fmp4_packager/fmp4_packager.cpp
index 7d76837d9..2fa4a6617 100644
--- a/src/projects/modules/containers/bmff/fmp4_packager/fmp4_packager.cpp
+++ b/src/projects/modules/containers/bmff/fmp4_packager/fmp4_packager.cpp
@@ -13,8 +13,8 @@
#include
#include
-#include
-#include
+#include
+#include
namespace bmff
{
diff --git a/src/projects/modules/containers/mpegts/mpegts_packetizer.cpp b/src/projects/modules/containers/mpegts/mpegts_packetizer.cpp
index befa4e4cc..1d5d9aa29 100644
--- a/src/projects/modules/containers/mpegts/mpegts_packetizer.cpp
+++ b/src/projects/modules/containers/mpegts/mpegts_packetizer.cpp
@@ -160,6 +160,11 @@ namespace mpegts
return false;
}
+ if (media_packet->GetMediaType() == cmn::MediaType::Data && media_packet->GetBitstreamFormat() != cmn::BitstreamFormat::ID3v2)
+ {
+ return false;
+ }
+
auto pid = GetElementaryPid(media_packet->GetTrackId());
auto pes = Pes::Build(pid, track, media_packet);
if (pes == nullptr)
diff --git a/src/projects/modules/data_format/AMS.mk b/src/projects/modules/data_format/AMS.mk
new file mode 100644
index 000000000..b757d5af6
--- /dev/null
+++ b/src/projects/modules/data_format/AMS.mk
@@ -0,0 +1,3 @@
+LOCAL_PATH := $(call get_local_path)
+
+include $(BUILD_SUB_AMS)
\ No newline at end of file
diff --git a/src/projects/modules/data_format/cue_event/AMS.mk b/src/projects/modules/data_format/cue_event/AMS.mk
new file mode 100644
index 000000000..c0bf662c3
--- /dev/null
+++ b/src/projects/modules/data_format/cue_event/AMS.mk
@@ -0,0 +1,8 @@
+LOCAL_PATH := $(call get_local_path)
+include $(DEFAULT_VARIABLES)
+
+LOCAL_TARGET := cue_event
+
+$(call add_pkg_config,srt)
+
+include $(BUILD_STATIC_LIBRARY)
\ No newline at end of file
diff --git a/src/projects/modules/data_format/cue_event/cue_event.cpp b/src/projects/modules/data_format/cue_event/cue_event.cpp
new file mode 100644
index 000000000..7a1da3d8b
--- /dev/null
+++ b/src/projects/modules/data_format/cue_event/cue_event.cpp
@@ -0,0 +1,59 @@
+//==============================================================================
+//
+// OvenMediaEngine
+//
+// Created by Getroot
+// Copyright (c) 2024 AirenSoft. All rights reserved.
+//
+//==============================================================================
+#include "cue_event.h"
+
+std::shared_ptr CueEvent::Create(CueType cue_type, uint32_t duration_sec)
+{
+ return std::make_shared(cue_type, duration_sec);
+}
+
+std::shared_ptr CueEvent::Parse(const std::shared_ptr &data)
+{
+ if (data == nullptr)
+ {
+ return nullptr;
+ }
+
+ ov::ByteStream stream(data);
+
+ CueType cue_type = static_cast(stream.Read8());
+ uint32_t duration_msec = stream.ReadBE32();
+
+ return Create(cue_type, duration_msec);
+}
+
+CueEvent::CueType CueEvent::GetCueTypeByName(ov::String type)
+{
+ if (type.UpperCaseString() == "OUT")
+ {
+ return CueType::OUT;
+ }
+ else if (type.UpperCaseString() == "IN")
+ {
+ return CueType::IN;
+ }
+
+ return CueType::Unknown;
+}
+
+CueEvent::CueEvent(CueType cue_type, uint32_t duration_sec)
+{
+ _cue_type = cue_type;
+ _duration_msec = duration_sec;
+}
+
+std::shared_ptr CueEvent::Serialize() const
+{
+ ov::ByteStream stream(5);
+
+ stream.Write8(static_cast(_cue_type));
+ stream.WriteBE32(_duration_msec);
+
+ return stream.GetDataPointer();
+}
\ No newline at end of file
diff --git a/src/projects/modules/data_format/cue_event/cue_event.h b/src/projects/modules/data_format/cue_event/cue_event.h
new file mode 100644
index 000000000..f7e045b7d
--- /dev/null
+++ b/src/projects/modules/data_format/cue_event/cue_event.h
@@ -0,0 +1,43 @@
+//==============================================================================
+//
+// OvenMediaEngine
+//
+// Created by Getroot
+// Copyright (c) 2024 AirenSoft. All rights reserved.
+//
+//==============================================================================
+#pragma once
+
+#include
+
+/*
+Data Structure
+
+CueType : 8 bits
+Duration - 32 bits / milliseconds, only positive value
+
+*/
+
+class CueEvent
+{
+public:
+ enum class CueType : uint8_t
+ {
+ Unknown = 0,
+ OUT,
+ IN
+ };
+
+ static std::shared_ptr Create(CueType cue_type, uint32_t duration_msec);
+ static std::shared_ptr Parse(const std::shared_ptr &data);
+ static CueType GetCueTypeByName(ov::String type);
+
+ CueEvent(CueType cue_type, uint32_t duration_sec);
+ ~CueEvent() = default;
+
+ std::shared_ptr Serialize() const;
+
+private:
+ CueType _cue_type;
+ uint32_t _duration_msec;
+};
\ No newline at end of file
diff --git a/src/projects/modules/id3v2/AMS.mk b/src/projects/modules/data_format/id3v2/AMS.mk
similarity index 100%
rename from src/projects/modules/id3v2/AMS.mk
rename to src/projects/modules/data_format/id3v2/AMS.mk
diff --git a/src/projects/modules/id3v2/frames/id3v2_frames.h b/src/projects/modules/data_format/id3v2/frames/id3v2_frames.h
similarity index 100%
rename from src/projects/modules/id3v2/frames/id3v2_frames.h
rename to src/projects/modules/data_format/id3v2/frames/id3v2_frames.h
diff --git a/src/projects/modules/id3v2/frames/id3v2_priv_frame.h b/src/projects/modules/data_format/id3v2/frames/id3v2_priv_frame.h
similarity index 100%
rename from src/projects/modules/id3v2/frames/id3v2_priv_frame.h
rename to src/projects/modules/data_format/id3v2/frames/id3v2_priv_frame.h
diff --git a/src/projects/modules/id3v2/frames/id3v2_text_frame.h b/src/projects/modules/data_format/id3v2/frames/id3v2_text_frame.h
similarity index 100%
rename from src/projects/modules/id3v2/frames/id3v2_text_frame.h
rename to src/projects/modules/data_format/id3v2/frames/id3v2_text_frame.h
diff --git a/src/projects/modules/id3v2/frames/id3v2_txxx_frame.h b/src/projects/modules/data_format/id3v2/frames/id3v2_txxx_frame.h
similarity index 100%
rename from src/projects/modules/id3v2/frames/id3v2_txxx_frame.h
rename to src/projects/modules/data_format/id3v2/frames/id3v2_txxx_frame.h
diff --git a/src/projects/modules/id3v2/id3v2.cpp b/src/projects/modules/data_format/id3v2/id3v2.cpp
similarity index 100%
rename from src/projects/modules/id3v2/id3v2.cpp
rename to src/projects/modules/data_format/id3v2/id3v2.cpp
diff --git a/src/projects/modules/id3v2/id3v2.h b/src/projects/modules/data_format/id3v2/id3v2.h
similarity index 100%
rename from src/projects/modules/id3v2/id3v2.h
rename to src/projects/modules/data_format/id3v2/id3v2.h
diff --git a/src/projects/modules/id3v2/id3v2_frame.cpp b/src/projects/modules/data_format/id3v2/id3v2_frame.cpp
similarity index 100%
rename from src/projects/modules/id3v2/id3v2_frame.cpp
rename to src/projects/modules/data_format/id3v2/id3v2_frame.cpp
diff --git a/src/projects/modules/id3v2/id3v2_frame.h b/src/projects/modules/data_format/id3v2/id3v2_frame.h
similarity index 100%
rename from src/projects/modules/id3v2/id3v2_frame.h
rename to src/projects/modules/data_format/id3v2/id3v2_frame.h
diff --git a/src/projects/providers/rtmp/rtmp_stream.cpp b/src/projects/providers/rtmp/rtmp_stream.cpp
index 331ac8607..ca8c8105a 100644
--- a/src/projects/providers/rtmp/rtmp_stream.cpp
+++ b/src/projects/providers/rtmp/rtmp_stream.cpp
@@ -13,8 +13,8 @@
#include
#include
#include
-#include
-#include
+#include
+#include
#include
#include "base/info/application.h"