Merge pull request #371 from Palakis/pause-recording

Recording Pause support
This commit is contained in:
Stéphane Lepin 2019-09-03 21:28:55 +02:00 committed by GitHub
commit 8b841f026e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 215 additions and 64 deletions

View File

@ -22,6 +22,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <util/platform.h>
#include "obs-websocket.h"
#include "Utils.h"
@ -764,3 +766,51 @@ obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool include
return enumParams.filters;
}
void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, PauseRecordingFunction* pauseRecFuncPtr)
{
void* frontendApi = os_dlopen("obs-frontend-api");
if (recPausedFuncPtr) {
*recPausedFuncPtr = (RecordingPausedFunction)os_dlsym(frontendApi, "obs_frontend_recording_paused");
}
if (pauseRecFuncPtr) {
*pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause");
}
os_dlclose(frontendApi);
}
bool Utils::RecordingPauseSupported()
{
RecordingPausedFunction recordingPaused = nullptr;
PauseRecordingFunction pauseRecording = nullptr;
getPauseRecordingFunctions(&recordingPaused, &pauseRecording);
return (recordingPaused && pauseRecording);
}
bool Utils::RecordingPaused()
{
RecordingPausedFunction recordingPaused = nullptr;
getPauseRecordingFunctions(&recordingPaused, nullptr);
if (recordingPaused == nullptr) {
return false;
}
return recordingPaused();
}
void Utils::PauseRecording(bool pause)
{
PauseRecordingFunction pauseRecording = nullptr;
getPauseRecordingFunctions(nullptr, &pauseRecording);
if (pauseRecording == nullptr) {
return;
}
pauseRecording(pause);
}

View File

@ -31,6 +31,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs-module.h>
#include <util/config-file.h>
typedef void(*PauseRecordingFunction)(bool);
typedef bool(*RecordingPausedFunction)();
class Utils {
public:
static obs_data_array_t* StringListToArray(char** strings, const char* key);
@ -78,4 +81,8 @@ class Utils {
static const char* GetFilenameFormatting();
static bool SetFilenameFormatting(const char* filenameFormatting);
static bool RecordingPauseSupported();
static bool RecordingPaused();
static void PauseRecording(bool pause);
};

View File

@ -17,7 +17,9 @@
* with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <inttypes.h>
#include <util/platform.h>
#include <media-io/video-io.h>
#include <QtWidgets/QPushButton>
@ -29,7 +31,7 @@
#define STATUS_INTERVAL 2000
const char* nsToTimestamp(uint64_t ns) {
QString nsToTimestamp(uint64_t ns) {
uint64_t ms = ns / 1000000ULL;
uint64_t secs = ms / 1000ULL;
uint64_t minutes = secs / 60ULL;
@ -39,10 +41,7 @@ const char* nsToTimestamp(uint64_t ns) {
uint64_t secsPart = secs % 60ULL;
uint64_t msPart = ms % 1000ULL;
char* ts = (char*)bmalloc(64);
sprintf(ts, "%02llu:%02llu:%02llu.%03llu", hoursPart, minutesPart, secsPart, msPart);
return ts;
return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
}
const char* sourceTypeToString(obs_source_type type) {
@ -75,7 +74,8 @@ const char* calldata_get_string(const calldata_t* data, const char* name) {
WSEvents::WSEvents(WSServerPtr srv) :
_srv(srv),
_streamStarttime(0),
_recStarttime(0),
_lastBytesSent(0),
_lastBytesSentTime(0),
HeartbeatIsActive(false),
pulse(false)
{
@ -206,6 +206,14 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
owner->OnRecordingStopped();
break;
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
owner->OnRecordingPaused();
break;
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
owner->OnRecordingResumed();
break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING:
owner->OnReplayStarting();
break;
@ -247,17 +255,14 @@ void WSEvents::broadcastUpdate(const char* updateType,
OBSDataAutoRelease update = obs_data_create();
obs_data_set_string(update, "update-type", updateType);
const char* ts = nullptr;
if (obs_frontend_streaming_active()) {
ts = nsToTimestamp(os_gettime_ns() - _streamStarttime);
obs_data_set_string(update, "stream-timecode", ts);
bfree((void*)ts);
QString streamingTimecode = getStreamingTimecode();
obs_data_set_string(update, "stream-timecode", streamingTimecode.toUtf8().constData());
}
if (obs_frontend_recording_active()) {
ts = nsToTimestamp(os_gettime_ns() - _recStarttime);
obs_data_set_string(update, "rec-timecode", ts);
bfree((void*)ts);
QString recordingTimecode = getRecordingTimecode();
obs_data_set_string(update, "rec-timecode", recordingTimecode.toUtf8().constData());
}
if (additionalFields)
@ -362,26 +367,34 @@ void WSEvents::unhookTransitionBeginEvent() {
obs_frontend_source_list_free(&transitions);
}
uint64_t WSEvents::GetStreamingTime() {
if (obs_frontend_streaming_active())
return (os_gettime_ns() - _streamStarttime);
else
uint64_t getOutputRunningTime(obs_output_t* output) {
if (!output || !obs_output_active(output)) {
return 0;
}
video_t* video = obs_output_video(output);
uint64_t frameTimeNs = video_output_get_frame_time(video);
int totalFrames = obs_output_get_total_frames(output);
return (((uint64_t)totalFrames) * frameTimeNs);
}
const char* WSEvents::GetStreamingTimecode() {
return nsToTimestamp(GetStreamingTime());
uint64_t WSEvents::getStreamingTime() {
OBSOutputAutoRelease streamingOutput = obs_frontend_get_streaming_output();
return getOutputRunningTime(streamingOutput);
}
uint64_t WSEvents::GetRecordingTime() {
if (obs_frontend_recording_active())
return (os_gettime_ns() - _recStarttime);
else
return 0;
uint64_t WSEvents::getRecordingTime() {
OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output();
return getOutputRunningTime(recordingOutput);
}
const char* WSEvents::GetRecordingTimecode() {
return nsToTimestamp(GetRecordingTime());
QString WSEvents::getStreamingTimecode() {
return nsToTimestamp(getStreamingTime());
}
QString WSEvents::getRecordingTimecode() {
return nsToTimestamp(getRecordingTime());
}
/**
@ -534,6 +547,7 @@ void WSEvents::OnStreamStarting() {
void WSEvents::OnStreamStarted() {
_streamStarttime = os_gettime_ns();
_lastBytesSent = 0;
broadcastUpdate("StreamStarted");
}
@ -550,7 +564,6 @@ void WSEvents::OnStreamStarted() {
void WSEvents::OnStreamStopping() {
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "preview-only", false);
broadcastUpdate("StreamStopping", data);
}
@ -564,6 +577,7 @@ void WSEvents::OnStreamStopping() {
*/
void WSEvents::OnStreamStopped() {
_streamStarttime = 0;
broadcastUpdate("StreamStopped");
}
@ -588,7 +602,6 @@ void WSEvents::OnRecordingStarting() {
* @since 0.3
*/
void WSEvents::OnRecordingStarted() {
_recStarttime = os_gettime_ns();
broadcastUpdate("RecordingStarted");
}
@ -613,10 +626,33 @@ void WSEvents::OnRecordingStopping() {
* @since 0.3
*/
void WSEvents::OnRecordingStopped() {
_recStarttime = 0;
broadcastUpdate("RecordingStopped");
}
/**
* Current recording paused
*
* @api events
* @name RecordingPaused
* @category recording
* @since 4.7.0
*/
void WSEvents::OnRecordingPaused() {
broadcastUpdate("RecordingPaused");
}
/**
* Current recording resumed
*
* @api events
* @name RecordingResumed
* @category recording
* @since 4.7.0
*/
void WSEvents::OnRecordingResumed() {
broadcastUpdate("RecordingResumed");
}
/**
* A request to start the replay buffer has been issued.
*
@ -708,6 +744,7 @@ void WSEvents::OnExit() {
void WSEvents::StreamStatus() {
bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = Utils::RecordingPaused();
bool replayBufferActive = obs_frontend_replay_buffer_active();
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
@ -734,9 +771,7 @@ void WSEvents::StreamStatus() {
_lastBytesSent = bytesSent;
_lastBytesSentTime = bytesSentTime;
uint64_t totalStreamTime =
(os_gettime_ns() - _streamStarttime) / 1000000000;
uint64_t totalStreamTime = (getStreamingTime() / 1000000000ULL);
int totalFrames = obs_output_get_total_frames(streamOutput);
int droppedFrames = obs_output_get_frames_dropped(streamOutput);
@ -746,6 +781,7 @@ void WSEvents::StreamStatus() {
obs_data_set_bool(data, "streaming", streamingActive);
obs_data_set_bool(data, "recording", recordingActive);
obs_data_set_bool(data, "recording-paused", recordingPaused);
obs_data_set_bool(data, "replay-buffer-active", replayBufferActive);
obs_data_set_int(data, "bytes-per-sec", bytesPerSec);
@ -792,6 +828,7 @@ void WSEvents::Heartbeat() {
bool streamingActive = obs_frontend_streaming_active();
bool recordingActive = obs_frontend_recording_active();
bool recordingPaused = Utils::RecordingPaused();
OBSDataAutoRelease data = obs_data_create();
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
@ -807,16 +844,15 @@ void WSEvents::Heartbeat() {
obs_data_set_bool(data, "streaming", streamingActive);
if (streamingActive) {
uint64_t totalStreamTime = (os_gettime_ns() - _streamStarttime) / 1000000000;
obs_data_set_int(data, "total-stream-time", totalStreamTime);
obs_data_set_int(data, "total-stream-time", (getStreamingTime() / 1000000000ULL));
obs_data_set_int(data, "total-stream-bytes", (uint64_t)obs_output_get_total_bytes(streamOutput));
obs_data_set_int(data, "total-stream-frames", obs_output_get_total_frames(streamOutput));
}
obs_data_set_bool(data, "recording", recordingActive);
obs_data_set_bool(data, "recording-paused", recordingPaused);
if (recordingActive) {
uint64_t totalRecordTime = (os_gettime_ns() - _recStarttime) / 1000000000;
obs_data_set_int(data, "total-record-time", totalRecordTime);
obs_data_set_int(data, "total-record-time", (getRecordingTime() / 1000000000ULL));
obs_data_set_int(data, "total-record-bytes", (uint64_t)obs_output_get_total_bytes(recordOutput));
obs_data_set_int(data, "total-record-frames", obs_output_get_total_frames(recordOutput));
}

View File

@ -43,10 +43,12 @@ public:
void hookTransitionBeginEvent();
void unhookTransitionBeginEvent();
uint64_t GetStreamingTime();
const char* GetStreamingTimecode();
uint64_t GetRecordingTime();
const char* GetRecordingTimecode();
uint64_t getStreamingTime();
uint64_t getRecordingTime();
QString getStreamingTimecode();
QString getRecordingTimecode();
obs_data_t* GetStats();
void OnBroadcastCustomMessage(QString realm, obs_data_t* data);
@ -67,7 +69,6 @@ private:
bool pulse;
uint64_t _streamStarttime;
uint64_t _recStarttime;
uint64_t _lastBytesSent;
uint64_t _lastBytesSentTime;
@ -95,6 +96,8 @@ private:
void OnRecordingStarted();
void OnRecordingStopping();
void OnRecordingStopped();
void OnRecordingPaused();
void OnRecordingResumed();
void OnReplayStarting();
void OnReplayStarted();

View File

@ -24,7 +24,7 @@
#include "WSRequestHandler.h"
QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageMap{
{ "GetVersion", WSRequestHandler::HandleGetVersion },
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
{ "Authenticate", WSRequestHandler::HandleAuthenticate },
@ -57,10 +57,14 @@ QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageM
{ "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
{ "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
{ "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
{ "StartStreaming", WSRequestHandler::HandleStartStreaming },
{ "StopStreaming", WSRequestHandler::HandleStopStreaming },
{ "StartRecording", WSRequestHandler::HandleStartRecording },
{ "StopRecording", WSRequestHandler::HandleStopRecording },
{ "PauseRecording", WSRequestHandler::HandlePauseRecording },
{ "ResumeRecording", WSRequestHandler::HandleResumeRecording },
{ "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
{ "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },

View File

@ -103,10 +103,14 @@ class WSRequestHandler : public QObject {
static HandlerResponse HandleGetStreamingStatus(WSRequestHandler* req);
static HandlerResponse HandleStartStopStreaming(WSRequestHandler* req);
static HandlerResponse HandleStartStopRecording(WSRequestHandler* req);
static HandlerResponse HandleStartStreaming(WSRequestHandler* req);
static HandlerResponse HandleStopStreaming(WSRequestHandler* req);
static HandlerResponse HandleStartRecording(WSRequestHandler* req);
static HandlerResponse HandleStopRecording(WSRequestHandler* req);
static HandlerResponse HandlePauseRecording(WSRequestHandler* req);
static HandlerResponse HandleResumeRecording(WSRequestHandler* req);
static HandlerResponse HandleStartStopReplayBuffer(WSRequestHandler* req);
static HandlerResponse HandleStartReplayBuffer(WSRequestHandler* req);

View File

@ -1,6 +1,20 @@
#include "WSRequestHandler.h"
#include <util/platform.h>
#include "Utils.h"
#include "WSRequestHandler.h"
HandlerResponse ifCanPause(WSRequestHandler* req, std::function<HandlerResponse()> callback)
{
if (!obs_frontend_recording_active()) {
return req->SendErrorResponse("recording is not active");
}
if (!Utils::RecordingPauseSupported()) {
return req->SendErrorResponse("recording pauses are not available in this version of OBS Studio");
}
return callback();
}
/**
* Toggle recording on or off.
@ -11,11 +25,7 @@
* @since 0.3
*/
HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active())
obs_frontend_recording_stop();
else
obs_frontend_recording_start();
(obs_frontend_recording_active() ? obs_frontend_recording_stop() : obs_frontend_recording_start());
return req->SendOKResponse();
}
@ -29,12 +39,12 @@ HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active() == false) {
obs_frontend_recording_start();
return req->SendOKResponse();
} else {
if (obs_frontend_recording_active()) {
return req->SendErrorResponse("recording already active");
}
obs_frontend_recording_start();
return req->SendOKResponse();
}
/**
@ -47,12 +57,52 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
* @since 4.1.0
*/
HandlerResponse WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
if (obs_frontend_recording_active() == true) {
obs_frontend_recording_stop();
return req->SendOKResponse();
} else {
if (!obs_frontend_recording_active()) {
return req->SendErrorResponse("recording not active");
}
obs_frontend_recording_stop();
return req->SendOKResponse();
}
/**
* Pause the current recording.
* Returns an error if recording is not active or already paused.
*
* @api requests
* @name PauseRecording
* @category recording
* @since 4.7.0
*/
HandlerResponse WSRequestHandler::HandlePauseRecording(WSRequestHandler* req) {
return ifCanPause(req, [req]() {
if (Utils::RecordingPaused()) {
return req->SendErrorResponse("recording already paused");
}
Utils::PauseRecording(true);
return req->SendOKResponse();
});
}
/**
* Resume/unpause the current recording (if paused).
* Returns an error if recording is not active or not paused.
*
* @api requests
* @name ResumeRecording
* @category recording
* @since 4.7.0
*/
HandlerResponse WSRequestHandler::HandleResumeRecording(WSRequestHandler* req) {
return ifCanPause(req, [req]() {
if (!Utils::RecordingPaused()) {
return req->SendErrorResponse("recording is not paused");
}
Utils::PauseRecording(false);
return req->SendOKResponse();
});
}
/**
@ -63,7 +113,6 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
* in progress, the change won't be applied immediately and will be
* effective on the next recording.
*
*
* @param {String} `rec-folder` Path of the recording folder.
*
* @api requests

View File

@ -26,19 +26,17 @@ HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req
OBSDataAutoRelease data = obs_data_create();
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
obs_data_set_bool(data, "recording-paused", Utils::RecordingPaused());
obs_data_set_bool(data, "preview-only", false);
const char* tc = nullptr;
if (obs_frontend_streaming_active()) {
tc = events->GetStreamingTimecode();
obs_data_set_string(data, "stream-timecode", tc);
bfree((void*)tc);
QString streamingTimecode = events->getStreamingTimecode();
obs_data_set_string(data, "stream-timecode", streamingTimecode.toUtf8().constData());
}
if (obs_frontend_recording_active()) {
tc = events->GetRecordingTimecode();
obs_data_set_string(data, "rec-timecode", tc);
bfree((void*)tc);
QString recordingTimecode = events->getRecordingTimecode();
obs_data_set_string(data, "rec-timecode", recordingTimecode.toUtf8().constData());
}
return req->SendOKResponse(data);