From c0ce2b800631b3d9496f08684941199113321756 Mon Sep 17 00:00:00 2001 From: Il`ya Semyonov Date: Thu, 11 Mar 2021 01:22:28 +0100 Subject: [PATCH 1/4] add file audio device descriptor to provide callbacks and settings direct from python to file audio device (close #16) --- pytgcalls/pytgcalls/group_call.py | 13 +++-- pytgcalls/pytgcalls/group_call_native.py | 34 +++++--------- tgcalls/CMakeLists.txt | 1 + tgcalls/src/FileAudioDeviceDescriptor.h | 10 ++++ tgcalls/src/NativeInstance.cpp | 47 +++++++++---------- tgcalls/src/NativeInstance.h | 27 ++++++----- tgcalls/src/tgcalls.cpp | 8 +++- .../lib_tgcalls/tgcalls/FileAudioDevice.cpp | 39 +++++++-------- .../lib_tgcalls/tgcalls/FileAudioDevice.h | 13 ++--- .../tgcalls/group/GroupInstanceImpl.cpp | 22 +++------ .../tgcalls/group/GroupInstanceImpl.h | 5 +- 11 files changed, 105 insertions(+), 114 deletions(-) create mode 100644 tgcalls/src/FileAudioDeviceDescriptor.h diff --git a/pytgcalls/pytgcalls/group_call.py b/pytgcalls/pytgcalls/group_call.py index aeab0c94..f25c2961 100644 --- a/pytgcalls/pytgcalls/group_call.py +++ b/pytgcalls/pytgcalls/group_call.py @@ -21,6 +21,7 @@ import pyrogram +import tgcalls from pytgcalls import GroupCallNative @@ -35,17 +36,21 @@ def __init__( path_to_log_file='group_call.log' ): super().__init__(client, enable_logs_to_console, path_to_log_file) - self.__use_file_audio_device = True self._input_filename = input_filename or '' self._output_filename = output_filename or '' + def __create_file_audio_device_descriptor(self): + file_audio_device_descriptor = tgcalls.FileAudioDeviceDescriptor() + file_audio_device_descriptor.getInputFilename = self.__get_input_filename_callback + file_audio_device_descriptor.getOutputFilename = self.__get_output_filename_callback + + return file_audio_device_descriptor + async def start(self, group: Union[str, int], enable_action=True): await super().start(group, enable_action) - await self._start_group_call( - self.__use_file_audio_device, self.__get_input_filename_callback, self.__get_output_filename_callback - ) + await self._start_group_call(self.__create_file_audio_device_descriptor()) def stop_playout(self): self.input_filename = '' diff --git a/pytgcalls/pytgcalls/group_call_native.py b/pytgcalls/pytgcalls/group_call_native.py index cb9997a5..562d9df0 100644 --- a/pytgcalls/pytgcalls/group_call_native.py +++ b/pytgcalls/pytgcalls/group_call_native.py @@ -100,9 +100,16 @@ def __deinit_native_instance(self): del tmp logger.debug('Native instance destroyed.') - def __setup_native_instance(self): + def __create_and_setup_native_instance(self): logger.debug('Create a new native instance..') - native_instance = tgcalls.NativeInstance() + native_instance = tgcalls.NativeInstance(self.enable_logs_to_console, self.path_to_log_file) + + native_instance.setupGroupCall( + self.__emit_join_payload_callback, + self.__network_state_updated_callback, + self.__participant_descriptions_required_callback + ) + logger.debug('Native instance created.') return native_instance @@ -240,7 +247,7 @@ async def start(self, group: Union[str, int], enable_action=True): handler_group = await self.__set_and_get_handler_group() self.client.add_handler(self._update_handler, handler_group) - self.__native_instance = self.__setup_native_instance() + self.__native_instance = self.__create_and_setup_native_instance() self.enable_action = enable_action self.my_user_id = await self.client.storage.user_id() @@ -252,26 +259,9 @@ async def reconnect(self): await self.stop() await self.start(chat_peer, enable_action) - async def _start_group_call( - self, - use_file_audio_device: bool, - get_input_filename_callback: Callable, - get_output_filename_callback: Callable - ): + async def _start_group_call(self, *args): logger.debug('Start native group call..') - # TODO move callbacks to __setup_native_instance - self.__native_instance.startGroupCall( - self.enable_logs_to_console, - self.path_to_log_file, - - use_file_audio_device, - - self.__emit_join_payload_callback, - self.__network_state_updated_callback, - self.__participant_descriptions_required_callback, - get_input_filename_callback, - get_output_filename_callback - ) + self.__native_instance.startGroupCall(*args) def set_is_mute(self, is_muted: bool): logger.debug(f'Set is muted. New value: {is_muted}.') diff --git a/tgcalls/CMakeLists.txt b/tgcalls/CMakeLists.txt index 1e5a331a..74aeec96 100644 --- a/tgcalls/CMakeLists.txt +++ b/tgcalls/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND SOURCES ${src_loc}/RtcServer.h ${src_loc}/RtcServer.cpp ${src_loc}/InstanceHolder.h + ${src_loc}/FileAudioDeviceDescriptor.h ) pybind11_add_module(tgcalls ${SOURCES}) diff --git a/tgcalls/src/FileAudioDeviceDescriptor.h b/tgcalls/src/FileAudioDeviceDescriptor.h new file mode 100644 index 00000000..a426a741 --- /dev/null +++ b/tgcalls/src/FileAudioDeviceDescriptor.h @@ -0,0 +1,10 @@ +#pragma once + +#include + + +class FileAudioDeviceDescriptor { +public: + std::function _getInputFilename = nullptr; + std::function _getOutputFilename = nullptr; +}; diff --git a/tgcalls/src/NativeInstance.cpp b/tgcalls/src/NativeInstance.cpp index 27d8ef61..fdbe35e2 100644 --- a/tgcalls/src/NativeInstance.cpp +++ b/tgcalls/src/NativeInstance.cpp @@ -3,6 +3,7 @@ #include #include "NativeInstance.h" +#include "FileAudioDeviceDescriptor.h" namespace py = pybind11; @@ -11,7 +12,8 @@ std::string copyright = "Copyright (C) 2020-2021 Il`ya (Marshal) &emitJoinPayloadCallback, - std::function &networkStateUpdated, - std::function const &)> &participantDescriptionsRequired, - std::function &getInputFilename, - std::function &getOutputFilename) { - // TODO move to the constructor - _emitJoinPayloadCallback = emitJoinPayloadCallback; - _networkStateUpdated = networkStateUpdated; - _participantDescriptionsRequired = participantDescriptionsRequired; - - _getInputFilename = getInputFilename; - _getOutputFilename = getOutputFilename; +void NativeInstance::setupGroupCall( + std::function &emitJoinPayloadCallback, + std::function &networkStateUpdated, + std::function const &)> &participantDescriptionsRequired +) { + _emitJoinPayloadCallback = emitJoinPayloadCallback; + _networkStateUpdated = networkStateUpdated; + _participantDescriptionsRequired = participantDescriptionsRequired; +} + +void NativeInstance::startGroupCall(FileAudioDeviceDescriptor &fileAudioDeviceDescriptor) { + _fileAudioDeviceDescriptor = fileAudioDeviceDescriptor; tgcalls::GroupInstanceDescriptor descriptor { .config = tgcalls::GroupConfig { - .logPath = {std::move(logPath)}, - .logToStdErr = logToStdErr, + .logPath = {std::move(_logPath)}, + .logToStdErr = _logToStdErr, }, .networkStateUpdated = [=](bool is_connected) { _networkStateUpdated(is_connected); }, - .audioLevelsUpdated = [=](tgcalls::GroupLevelsUpdate const &update) {}, // TODO - .useFileAudioDevice = useFileAudioDevice, - .getInputFilename = [=]() { - return _getInputFilename(); - }, - .getOutputFilename = [=]() { - return _getOutputFilename(); - }, + .audioLevelsUpdated = [=](tgcalls::GroupLevelsUpdate const &update) {}, // TODO may be .participantDescriptionsRequired = [=](std::vector const &ssrcs) { _participantDescriptionsRequired(ssrcs); }, + .getFileAudioDeviceDescriptor = [=]() -> FileAudioDeviceDescriptor& { + return _fileAudioDeviceDescriptor; + } }; instanceHolder = std::make_unique(); diff --git a/tgcalls/src/NativeInstance.h b/tgcalls/src/NativeInstance.h index b495c453..06965132 100644 --- a/tgcalls/src/NativeInstance.h +++ b/tgcalls/src/NativeInstance.h @@ -3,6 +3,7 @@ #include #include "InstanceHolder.h" #include "RtcServer.h" +#include "FileAudioDeviceDescriptor.h" namespace py = pybind11; @@ -10,25 +11,29 @@ class NativeInstance { public: std::unique_ptr instanceHolder; + bool _logToStdErr; + string _logPath; + std::function &data)> signalingDataEmittedCallback; + std::function _emitJoinPayloadCallback = nullptr; std::function _networkStateUpdated = nullptr; std::function const &)> _participantDescriptionsRequired = nullptr; - std::function _getInputFilename = nullptr; - std::function _getOutputFilename = nullptr; - NativeInstance(); + FileAudioDeviceDescriptor _fileAudioDeviceDescriptor; + + NativeInstance(bool, string); ~NativeInstance(); void startCall(vector servers, std::array authKey, bool isOutgoing, std::string logPath); - void startGroupCall(bool logToStdErr, - string logPath, - bool useFileAudioDevice, - std::function &emitJoinPayloadCallback, - std::function &networkStateUpdated, - std::function const &)> &participantDescriptionsRequired, - std::function &getInputFilename, - std::function &getOutputFilename); + + void setupGroupCall( + std::function &, + std::function &, + std::function const &)> & + ); + + void startGroupCall(FileAudioDeviceDescriptor &); void stopGroupCall() const; void setIsMuted(bool isMuted) const; diff --git a/tgcalls/src/tgcalls.cpp b/tgcalls/src/tgcalls.cpp index 9df8afa4..567c1eae 100644 --- a/tgcalls/src/tgcalls.cpp +++ b/tgcalls/src/tgcalls.cpp @@ -148,9 +148,15 @@ PYBIND11_MODULE(tgcalls, m) { .def_readwrite("ssrcs", &tgcalls::GroupJoinPayloadVideoSourceGroup::ssrcs) .def_readwrite("semantics", &tgcalls::GroupJoinPayloadVideoSourceGroup::semantics); - py::class_(m, "NativeInstance") + py::class_(m, "FileAudioDeviceDescriptor") .def(py::init<>()) + .def_readwrite("getInputFilename", &FileAudioDeviceDescriptor::_getInputFilename) + .def_readwrite("getOutputFilename", &FileAudioDeviceDescriptor::_getOutputFilename); + + py::class_(m, "NativeInstance") + .def(py::init()) .def("startCall", &NativeInstance::startCall) + .def("setupGroupCall", &NativeInstance::setupGroupCall) .def("startGroupCall", &NativeInstance::startGroupCall) .def("stopGroupCall", &NativeInstance::stopGroupCall) .def("setIsMuted", &NativeInstance::setIsMuted) diff --git a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp index 1437a8ff..dbf0bb3a 100644 --- a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp +++ b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp @@ -20,8 +20,7 @@ const size_t kPlayoutBufferSize = const size_t kRecordingBufferSize = kRecordingFixedSampleRate / 100 * kRecordingNumChannels * 2; -FileAudioDevice::FileAudioDevice(std::function getInputFilename, - std::function getOutputFilename) +FileAudioDevice::FileAudioDevice(std::function getFileAudioDeviceDescriptor) : _ptrAudioBuffer(NULL), _recordingBuffer(NULL), _playoutBuffer(NULL), @@ -34,8 +33,7 @@ FileAudioDevice::FileAudioDevice(std::function getInputFilename, _recording(false), _lastCallPlayoutMillis(0), _lastCallRecordMillis(0), - _getInputFilename(std::move(getInputFilename)), - _getOutputFilename(std::move(getOutputFilename)) {} + _getFileAudioDeviceDescriptor(std::move(getFileAudioDeviceDescriptor)) {} FileAudioDevice::~FileAudioDevice() {} @@ -196,10 +194,11 @@ int32_t FileAudioDevice::StartPlayout() { } // PLAYOUT - if (!_getOutputFilename().empty()) { - _outputFile = webrtc::FileWrapper::OpenWriteOnly(_getOutputFilename().c_str()); + auto outputFilename = _getFileAudioDeviceDescriptor()._getOutputFilename(); + if (!outputFilename.empty()) { + _outputFile = webrtc::FileWrapper::OpenWriteOnly(outputFilename.c_str()); if (!_outputFile.is_open()) { - RTC_LOG(LS_ERROR) << "Failed to open playout file: " << _getOutputFilename(); + RTC_LOG(LS_ERROR) << "Failed to open playout file: " << outputFilename; _playing = false; delete[] _playoutBuffer; _playoutBuffer = NULL; @@ -212,8 +211,7 @@ int32_t FileAudioDevice::StartPlayout() { rtc::kRealtimePriority)); _ptrThreadPlay->Start(); - RTC_LOG(LS_INFO) << "Started playout capture to output file: " - << _getOutputFilename(); + RTC_LOG(LS_INFO) << "Started playout capture to output file: " << outputFilename; return 0; } @@ -254,11 +252,11 @@ int32_t FileAudioDevice::StartRecording() { _recordingBuffer = new int8_t[_recordingBufferSizeIn10MS]; } - if (!_getInputFilename().empty()) { - _inputFile = webrtc::FileWrapper::OpenReadOnly(_getInputFilename().c_str()); + auto inputFilename = _getFileAudioDeviceDescriptor()._getInputFilename(); + if (!inputFilename.empty()) { + _inputFile = webrtc::FileWrapper::OpenReadOnly(inputFilename.c_str()); if (!_inputFile.is_open()) { - RTC_LOG(LS_ERROR) << "Failed to open audio input file: " - << _getInputFilename(); + RTC_LOG(LS_ERROR) << "Failed to open audio input file: " << inputFilename; _recording = false; delete[] _recordingBuffer; _recordingBuffer = NULL; @@ -272,7 +270,7 @@ int32_t FileAudioDevice::StartRecording() { _ptrThreadRec->Start(); - RTC_LOG(LS_INFO) << "Started recording from input file: " << _getInputFilename(); + RTC_LOG(LS_INFO) << "Started recording from input file: " << inputFilename; return 0; } @@ -510,18 +508,15 @@ bool FileAudioDevice::RecThreadProcess() { rtc::scoped_refptr WrappedAudioDeviceModuleImpl::Create( AudioLayer audio_layer, webrtc::TaskQueueFactory *task_queue_factory, - std::function getInputFilename, - std::function getOutputFilename) { + std::function getFileAudioDeviceDescriptor) { RTC_LOG(INFO) << __FUNCTION__; - return WrappedAudioDeviceModuleImpl::CreateForTest( - audio_layer, task_queue_factory, std::move(getInputFilename), std::move(getOutputFilename)); + return WrappedAudioDeviceModuleImpl::CreateForTest(audio_layer, task_queue_factory, std::move(getFileAudioDeviceDescriptor)); } rtc::scoped_refptr WrappedAudioDeviceModuleImpl::CreateForTest( AudioLayer audio_layer, webrtc::TaskQueueFactory *task_queue_factory, - std::function getInputFilename, - std::function getOutputFilename) { + std::function getFileAudioDeviceDescriptor) { RTC_LOG(INFO) << __FUNCTION__; // The "AudioDeviceModule::kWindowsCoreAudio2" audio layer has its own @@ -545,9 +540,7 @@ rtc::scoped_refptr WrappedAudioDeviceModuleImpl:: // Create the platform-dependent implementation. auto created = audioDevice->CreatePlatformSpecificObjects(); if (audio_layer == kDummyAudio) { - // todo from descriptor of config. create methods to set new values - audioDevice->ResetAudioDevice(new FileAudioDevice( - std::move(getInputFilename), std::move(getOutputFilename))); + audioDevice->ResetAudioDevice(new FileAudioDevice(std::move(getFileAudioDeviceDescriptor))); } if (created == -1) { return nullptr; diff --git a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.h b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.h index bf231081..d5cda941 100644 --- a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.h +++ b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.h @@ -18,6 +18,7 @@ #include "rtc_base/synchronization/mutex.h" #include "rtc_base/system/file_wrapper.h" #include "rtc_base/time_utils.h" +#include "../../../src/FileAudioDeviceDescriptor.h" namespace rtc { class PlatformThread; @@ -35,8 +36,7 @@ class FileAudioDevice : public webrtc::AudioDeviceGeneric { // The input file should be a readable 48k stereo raw file, and the output // file should point to a writable location. The output format will also be // 48k stereo raw audio. - FileAudioDevice(std::function getInputFilename, - std::function getOutputFilename); + FileAudioDevice(std::function getFileAudioDeviceDescriptor); virtual ~FileAudioDevice(); @@ -198,8 +198,7 @@ class FileAudioDevice : public webrtc::AudioDeviceGeneric { webrtc::FileWrapper _outputFile; webrtc::FileWrapper _inputFile; - std::function _getInputFilename; - std::function _getOutputFilename; + std::function _getFileAudioDeviceDescriptor; }; @@ -208,12 +207,10 @@ class WrappedAudioDeviceModuleImpl : public webrtc::AudioDeviceModule { static rtc::scoped_refptr Create( AudioLayer audio_layer, webrtc::TaskQueueFactory* task_queue_factory, - std::function getInputFilename, - std::function getOutputFilename); + std::function getFileAudioDeviceDescriptor); static rtc::scoped_refptr CreateForTest( AudioLayer audio_layer, webrtc::TaskQueueFactory* task_queue_factory, - std::function getInputFilename, - std::function getOutputFilename); + std::function getFileAudioDeviceDescriptor); }; diff --git a/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.cpp b/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.cpp index b521b63b..c135dcb9 100644 --- a/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.cpp +++ b/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.cpp @@ -1585,11 +1585,9 @@ class GroupInstanceManager : public std::enable_shared_from_this(); do { @@ -1627,11 +1625,7 @@ class GroupInstanceManager : public std::enable_shared_from_this getInputFilename, - std::function getOutputFilename) { + bool createAudioDeviceModule(const webrtc::PeerConnectionFactoryDependencies &dependencies) { _adm_thread = dependencies.worker_thread; if (!_adm_thread) { return false; @@ -1641,8 +1635,7 @@ class GroupInstanceManager : public std::enable_shared_from_this &result) { _adm_use_withAudioDeviceModule = new rtc::RefCountedObject(result); @@ -1657,7 +1650,7 @@ class GroupInstanceManager : public std::enable_shared_from_this _joinPayload; @@ -3138,8 +3129,7 @@ class GroupInstanceManager : public std::enable_shared_from_this _errorParsingLogSink; - std::function _getInputFilename; - std::function _getOutputFilename; + std::function _getFileAudioDeviceDescriptor; }; GroupInstanceImpl::GroupInstanceImpl(GroupInstanceDescriptor &&descriptor) diff --git a/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.h b/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.h index dc07adc2..e55c90cb 100644 --- a/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.h +++ b/tgcalls/third_party/lib_tgcalls/tgcalls/group/GroupInstanceImpl.h @@ -8,6 +8,7 @@ #include #include "../Instance.h" +#include "../../../../src/FileAudioDeviceDescriptor.h" namespace webrtc { class AudioDeviceModule; @@ -49,14 +50,12 @@ struct GroupInstanceDescriptor { std::function audioLevelsUpdated; std::string initialInputDeviceId; std::string initialOutputDeviceId; - bool useFileAudioDevice; bool debugIgnoreMissingSsrcs = false; std::function(webrtc::TaskQueueFactory*)> createAudioDeviceModule; - std::function getInputFilename; - std::function getOutputFilename; std::shared_ptr videoCapture; std::function const &)> incomingVideoSourcesUpdated; std::function const &)> participantDescriptionsRequired; + std::function getFileAudioDeviceDescriptor; }; struct GroupJoinPayloadFingerprint { From 2c1cb0e666f40c751d9115de778fa4ea9efaaa19 Mon Sep 17 00:00:00 2001 From: Il`ya Semyonov Date: Thu, 11 Mar 2021 01:55:10 +0100 Subject: [PATCH 2/4] add ability to enable/disable loop playout at runtime (close #8) --- pytgcalls/pytgcalls/group_call.py | 9 ++++++++- pytgcalls/test.py | 18 ++++++++---------- tgcalls/src/FileAudioDeviceDescriptor.h | 2 ++ tgcalls/src/tgcalls.cpp | 3 ++- .../lib_tgcalls/tgcalls/FileAudioDevice.cpp | 5 ++++- 5 files changed, 24 insertions(+), 13 deletions(-) diff --git a/pytgcalls/pytgcalls/group_call.py b/pytgcalls/pytgcalls/group_call.py index f25c2961..8676d6af 100644 --- a/pytgcalls/pytgcalls/group_call.py +++ b/pytgcalls/pytgcalls/group_call.py @@ -33,10 +33,13 @@ def __init__( input_filename: str = '', output_filename: str = '', enable_logs_to_console=False, - path_to_log_file='group_call.log' + path_to_log_file='group_call.log', + play_on_repeat=True ): super().__init__(client, enable_logs_to_console, path_to_log_file) + self.play_on_repeat = play_on_repeat + self._input_filename = input_filename or '' self._output_filename = output_filename or '' @@ -44,6 +47,7 @@ def __create_file_audio_device_descriptor(self): file_audio_device_descriptor = tgcalls.FileAudioDeviceDescriptor() file_audio_device_descriptor.getInputFilename = self.__get_input_filename_callback file_audio_device_descriptor.getOutputFilename = self.__get_output_filename_callback + file_audio_device_descriptor.isEndlessPlayout = self.__is_endless_playout_callback return file_audio_device_descriptor @@ -83,3 +87,6 @@ def __get_input_filename_callback(self): def __get_output_filename_callback(self): return self._output_filename + + def __is_endless_playout_callback(self): + return self.play_on_repeat diff --git a/pytgcalls/test.py b/pytgcalls/test.py index aab28f4f..390ab319 100644 --- a/pytgcalls/test.py +++ b/pytgcalls/test.py @@ -445,22 +445,20 @@ async def main(client1, client2, make_out, make_inc): # @client2.on_message(filters.text & filters.outgoing & ~filters.edited & filters.command('test', prefixes='!')) # async def test(client, message): - group_call = GroupCall(client2, 'input.raw', '', True, '') - await group_call.start('@MarshalCh') + group_call = GroupCall(client2, '6s.raw', '', True, '') + await group_call.start('@MarshalCm') group_call.add_handler( network_status_changed_handler, GroupCallAction.NETWORK_STATUS_CHANGED ) - from random import randint - - e = True - while 1: - # break - await asyncio.sleep(5) - await group_call.set_my_volume(1 if e else 200) - e = not e + group_call.play_on_repeat = False + await asyncio.sleep(15) + group_call.restart_playout() + await asyncio.sleep(15) + group_call.play_on_repeat = True + group_call.restart_playout() ''' diff --git a/tgcalls/src/FileAudioDeviceDescriptor.h b/tgcalls/src/FileAudioDeviceDescriptor.h index a426a741..fee7d283 100644 --- a/tgcalls/src/FileAudioDeviceDescriptor.h +++ b/tgcalls/src/FileAudioDeviceDescriptor.h @@ -7,4 +7,6 @@ class FileAudioDeviceDescriptor { public: std::function _getInputFilename = nullptr; std::function _getOutputFilename = nullptr; + + std::function _isEndlessPlayout = nullptr; }; diff --git a/tgcalls/src/tgcalls.cpp b/tgcalls/src/tgcalls.cpp index 567c1eae..83d639fd 100644 --- a/tgcalls/src/tgcalls.cpp +++ b/tgcalls/src/tgcalls.cpp @@ -151,7 +151,8 @@ PYBIND11_MODULE(tgcalls, m) { py::class_(m, "FileAudioDeviceDescriptor") .def(py::init<>()) .def_readwrite("getInputFilename", &FileAudioDeviceDescriptor::_getInputFilename) - .def_readwrite("getOutputFilename", &FileAudioDeviceDescriptor::_getOutputFilename); + .def_readwrite("getOutputFilename", &FileAudioDeviceDescriptor::_getOutputFilename) + .def_readwrite("isEndlessPlayout", &FileAudioDeviceDescriptor::_isEndlessPlayout); py::class_(m, "NativeInstance") .def(py::init()) diff --git a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp index dbf0bb3a..e0b98ab4 100644 --- a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp +++ b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp @@ -484,8 +484,11 @@ bool FileAudioDevice::RecThreadProcess() { if (_inputFile.Read(_recordingBuffer, kRecordingBufferSize) > 0) { _ptrAudioBuffer->SetRecordedBuffer(_recordingBuffer, _recordingFramesIn10MS); - } else { + } else if (_getFileAudioDeviceDescriptor()._isEndlessPlayout()) { _inputFile.Rewind(); + } else { + mutex_.Unlock(); + return false; } _lastCallRecordMillis = currentTime; mutex_.Unlock(); From 489031d6ed8a9d28a5b16445caf3e29f626160fc Mon Sep 17 00:00:00 2001 From: Il`ya Semyonov Date: Thu, 11 Mar 2021 02:50:39 +0100 Subject: [PATCH 3/4] add on end of file callback (close #9) --- pytgcalls/pytgcalls/__init__.py | 8 +++--- pytgcalls/pytgcalls/action.py | 26 +++++++++++++++++++ pytgcalls/pytgcalls/dispatcher.py | 6 ++--- pytgcalls/pytgcalls/group_call.py | 19 ++++++++++++-- pytgcalls/pytgcalls/group_call_native.py | 18 ++++++------- pytgcalls/test.py | 6 ++++- tgcalls/src/FileAudioDeviceDescriptor.h | 2 ++ tgcalls/src/tgcalls.cpp | 3 ++- .../lib_tgcalls/tgcalls/FileAudioDevice.cpp | 10 +++++++ 9 files changed, 79 insertions(+), 19 deletions(-) create mode 100644 pytgcalls/pytgcalls/action.py diff --git a/pytgcalls/pytgcalls/__init__.py b/pytgcalls/pytgcalls/__init__.py index ba595d62..8d4f168f 100644 --- a/pytgcalls/pytgcalls/__init__.py +++ b/pytgcalls/pytgcalls/__init__.py @@ -17,10 +17,12 @@ # You should have received a copy of the GNU Lesser General Public License v3 # along with tgcalls. If not, see . -from pytgcalls.group_call_native import GroupCallNative, GroupCallAction -from pytgcalls.group_call import GroupCall +from pytgcalls.group_call_native import GroupCallNative, GroupCallNativeAction, GroupCallNativeDispatcherMixin +from pytgcalls.action import Action +from pytgcalls.group_call import GroupCall, GroupCallAction from pytgcalls.dispatcher import Dispatcher from pytgcalls.dispatcher_mixin import DispatcherMixin -__all__ = ['GroupCallNative', 'GroupCall', 'Dispatcher', 'DispatcherMixin', 'GroupCallAction'] +__all__ = ['GroupCallNative', 'GroupCall', 'Dispatcher', 'DispatcherMixin', 'Action', + 'GroupCallNativeAction', 'GroupCallNativeDispatcherMixin', 'GroupCallAction'] __version__ = '0.0.10' diff --git a/pytgcalls/pytgcalls/action.py b/pytgcalls/pytgcalls/action.py new file mode 100644 index 00000000..aa734e28 --- /dev/null +++ b/pytgcalls/pytgcalls/action.py @@ -0,0 +1,26 @@ +# tgcalls - Python binding for tgcalls (c++ lib by Telegram) +# pytgcalls - Library connecting python binding for tgcalls and Pyrogram +# Copyright (C) 2020-2021 Il`ya (Marshal) +# +# This file is part of tgcalls and pytgcalls. +# +# tgcalls and pytgcalls is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# tgcalls and pytgcalls 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 Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License v3 +# along with tgcalls. If not, see . + + +class Action: + def __set_name__(self, owner, name): + self.name = name + + def __get__(self, instance, owner): + return self.name diff --git a/pytgcalls/pytgcalls/dispatcher.py b/pytgcalls/pytgcalls/dispatcher.py index 6cad22e7..e1765c86 100644 --- a/pytgcalls/pytgcalls/dispatcher.py +++ b/pytgcalls/pytgcalls/dispatcher.py @@ -31,10 +31,10 @@ def __init__(self, available_actions): def __build_handler_storage(self): logger.debug('Build storage of handlers for dispatcher.') - return {action: [] for action in self.actions} + return {action: [] for action in dir(self.actions) if not action.startswith('_')} def add_handler(self, callback, action): - logger.debug('Add handler..') + logger.debug(f'Add handler to {action} action..') if not asyncio.iscoroutinefunction(callback): raise RuntimeError('Sync callback does not supported') @@ -52,7 +52,7 @@ def add_handler(self, callback, action): return callback def remove_handler(self, callback, action) -> bool: - logger.debug('Remove handler..') + logger.debug(f'Remove handler of {action} action..') try: handlers = self.__action_to_handlers[action] for i in range(len(handlers)): diff --git a/pytgcalls/pytgcalls/group_call.py b/pytgcalls/pytgcalls/group_call.py index 8676d6af..326eb62f 100644 --- a/pytgcalls/pytgcalls/group_call.py +++ b/pytgcalls/pytgcalls/group_call.py @@ -22,10 +22,20 @@ import pyrogram import tgcalls -from pytgcalls import GroupCallNative +from pytgcalls import GroupCallNative, GroupCallNativeAction, GroupCallNativeDispatcherMixin, Action -class GroupCall(GroupCallNative): +class GroupCallAction(GroupCallNativeAction): + PLAYOUT_ENDED = Action() + + +class GroupCallDispatcherMixin(GroupCallNativeDispatcherMixin): + + def on_playout_ended(self, func: callable): + return self.add_handler(func, GroupCallAction.PLAYOUT_ENDED) + + +class GroupCall(GroupCallNative, GroupCallDispatcherMixin): def __init__( self, @@ -37,6 +47,7 @@ def __init__( play_on_repeat=True ): super().__init__(client, enable_logs_to_console, path_to_log_file) + super(GroupCallDispatcherMixin, self).__init__(GroupCallAction) self.play_on_repeat = play_on_repeat @@ -48,6 +59,7 @@ def __create_file_audio_device_descriptor(self): file_audio_device_descriptor.getInputFilename = self.__get_input_filename_callback file_audio_device_descriptor.getOutputFilename = self.__get_output_filename_callback file_audio_device_descriptor.isEndlessPlayout = self.__is_endless_playout_callback + file_audio_device_descriptor.playoutEndedCallback = self.__playout_ended_callback return file_audio_device_descriptor @@ -90,3 +102,6 @@ def __get_output_filename_callback(self): def __is_endless_playout_callback(self): return self.play_on_repeat + + def __playout_ended_callback(self, input_filename: str): + self.trigger_handlers(GroupCallAction.PLAYOUT_ENDED, self, input_filename) diff --git a/pytgcalls/pytgcalls/group_call_native.py b/pytgcalls/pytgcalls/group_call_native.py index 562d9df0..e0bf3789 100644 --- a/pytgcalls/pytgcalls/group_call_native.py +++ b/pytgcalls/pytgcalls/group_call_native.py @@ -20,8 +20,7 @@ import asyncio import json import logging -from enum import Enum -from typing import Callable, List, Union +from typing import List, Union import pyrogram from pyrogram import raw @@ -31,6 +30,7 @@ from pyrogram.raw.types import InputPeerChannel, InputPeerChat import tgcalls +from .action import Action from .dispatcher_mixin import DispatcherMixin logger = logging.getLogger(__name__) @@ -39,14 +39,14 @@ int_ssrc = lambda ssrc: ssrc if ssrc < 2 ** 31 else ssrc - 2 ** 32 -class GroupCallAction(Enum): - NETWORK_STATUS_CHANGED = 0 +class GroupCallNativeAction: + NETWORK_STATUS_CHANGED = Action() -class GroupCallDispatcherMixin(DispatcherMixin): +class GroupCallNativeDispatcherMixin(DispatcherMixin): def on_network_status_changed(self, func: callable): - return self.add_handler(func, GroupCallAction.NETWORK_STATUS_CHANGED) + return self.add_handler(func, GroupCallNativeAction.NETWORK_STATUS_CHANGED) def parse_call_participant(participant_data): @@ -58,7 +58,7 @@ def parse_call_participant(participant_data): return native_participant -class GroupCallNative(GroupCallDispatcherMixin): +class GroupCallNative(GroupCallNativeDispatcherMixin): SEND_ACTION_UPDATE_EACH = 0.45 def __init__( @@ -67,7 +67,7 @@ def __init__( enable_logs_to_console: bool, path_to_log_file: str ): - super().__init__(GroupCallAction) + super().__init__(GroupCallNativeAction) self.client = client self.__native_instance = None @@ -311,7 +311,7 @@ def __network_state_updated_callback(self, state: bool): if self.enable_action: self.__start_status_worker() - self.trigger_handlers(GroupCallAction.NETWORK_STATUS_CHANGED, self, state) + self.trigger_handlers(GroupCallNativeAction.NETWORK_STATUS_CHANGED, self, state) logger.debug(f'New network state is {self.is_connected}.') diff --git a/pytgcalls/test.py b/pytgcalls/test.py index 390ab319..b3dd231d 100644 --- a/pytgcalls/test.py +++ b/pytgcalls/test.py @@ -445,7 +445,7 @@ async def main(client1, client2, make_out, make_inc): # @client2.on_message(filters.text & filters.outgoing & ~filters.edited & filters.command('test', prefixes='!')) # async def test(client, message): - group_call = GroupCall(client2, '6s.raw', '', True, '') + group_call = GroupCall(client2, '6s.raw', '', False, '') await group_call.start('@MarshalCm') group_call.add_handler( @@ -453,6 +453,10 @@ async def main(client1, client2, make_out, make_inc): GroupCallAction.NETWORK_STATUS_CHANGED ) + @group_call.on_playout_ended + async def playout_ended_handler(group_call, filename): + print(f'{filename} is ended') + group_call.play_on_repeat = False await asyncio.sleep(15) group_call.restart_playout() diff --git a/tgcalls/src/FileAudioDeviceDescriptor.h b/tgcalls/src/FileAudioDeviceDescriptor.h index fee7d283..f9ef6c9b 100644 --- a/tgcalls/src/FileAudioDeviceDescriptor.h +++ b/tgcalls/src/FileAudioDeviceDescriptor.h @@ -9,4 +9,6 @@ class FileAudioDeviceDescriptor { std::function _getOutputFilename = nullptr; std::function _isEndlessPlayout = nullptr; + + std::function _playoutEndedCallback = nullptr; }; diff --git a/tgcalls/src/tgcalls.cpp b/tgcalls/src/tgcalls.cpp index 83d639fd..3ff5036d 100644 --- a/tgcalls/src/tgcalls.cpp +++ b/tgcalls/src/tgcalls.cpp @@ -152,7 +152,8 @@ PYBIND11_MODULE(tgcalls, m) { .def(py::init<>()) .def_readwrite("getInputFilename", &FileAudioDeviceDescriptor::_getInputFilename) .def_readwrite("getOutputFilename", &FileAudioDeviceDescriptor::_getOutputFilename) - .def_readwrite("isEndlessPlayout", &FileAudioDeviceDescriptor::_isEndlessPlayout); + .def_readwrite("isEndlessPlayout", &FileAudioDeviceDescriptor::_isEndlessPlayout) + .def_readwrite("playoutEndedCallback", &FileAudioDeviceDescriptor::_playoutEndedCallback); py::class_(m, "NativeInstance") .def(py::init()) diff --git a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp index e0b98ab4..9ca4ac9d 100644 --- a/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp +++ b/tgcalls/third_party/lib_tgcalls/tgcalls/FileAudioDevice.cpp @@ -479,6 +479,7 @@ bool FileAudioDevice::RecThreadProcess() { int64_t currentTime = rtc::TimeMillis(); mutex_.Lock(); + auto inputFilename = _getFileAudioDeviceDescriptor()._getInputFilename(); if (_lastCallRecordMillis == 0 || currentTime - _lastCallRecordMillis >= 10) { if (_inputFile.is_open()) { if (_inputFile.Read(_recordingBuffer, kRecordingBufferSize) > 0) { @@ -486,8 +487,17 @@ bool FileAudioDevice::RecThreadProcess() { _recordingFramesIn10MS); } else if (_getFileAudioDeviceDescriptor()._isEndlessPlayout()) { _inputFile.Rewind(); + + if (_getFileAudioDeviceDescriptor()._playoutEndedCallback) { + _getFileAudioDeviceDescriptor()._playoutEndedCallback(inputFilename); + } } else { mutex_.Unlock(); + + if (_getFileAudioDeviceDescriptor()._playoutEndedCallback) { + _getFileAudioDeviceDescriptor()._playoutEndedCallback(inputFilename); + } + return false; } _lastCallRecordMillis = currentTime; From 9d4ee230d8088ca0205dde8987e28f4487bd2995 Mon Sep 17 00:00:00 2001 From: Il`ya Semyonov Date: Thu, 11 Mar 2021 03:04:02 +0100 Subject: [PATCH 4/4] bump versions --- pytgcalls/pytgcalls/__init__.py | 2 +- pytgcalls/setup.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pytgcalls/pytgcalls/__init__.py b/pytgcalls/pytgcalls/__init__.py index 8d4f168f..9dd92314 100644 --- a/pytgcalls/pytgcalls/__init__.py +++ b/pytgcalls/pytgcalls/__init__.py @@ -25,4 +25,4 @@ __all__ = ['GroupCallNative', 'GroupCall', 'Dispatcher', 'DispatcherMixin', 'Action', 'GroupCallNativeAction', 'GroupCallNativeDispatcherMixin', 'GroupCallAction'] -__version__ = '0.0.10' +__version__ = '0.0.11' diff --git a/pytgcalls/setup.py b/pytgcalls/setup.py index f9caba9e..4a1dc8dc 100644 --- a/pytgcalls/setup.py +++ b/pytgcalls/setup.py @@ -42,7 +42,7 @@ long_description=readme, long_description_content_type='text/markdown', packages=packages, - install_requires=['tgcalls == 0.0.6', 'pyrogram >= 1.1.13'], + install_requires=['tgcalls == 0.0.7', 'pyrogram >= 1.1.13'], python_requires="~=3.6", include_package_data=True, classifiers=[ diff --git a/setup.py b/setup.py index 2eeaf446..14d4e96f 100644 --- a/setup.py +++ b/setup.py @@ -117,7 +117,7 @@ def build_extension(self, ext): setup( name='tgcalls', - version=f'0.0.6', + version=f'0.0.7', author='Il\'ya Semyonov', author_email='ilya@marshal.by', url='https://github.com/MarshalX/tgcalls',