Cleanup unneeded dependencies
This commit is contained in:
parent
c727abe1ef
commit
65e08a9d41
30 changed files with 6 additions and 1857 deletions
6
Makefile
6
Makefile
|
|
@ -40,8 +40,10 @@ preparewheel:
|
||||||
pyclean .
|
pyclean .
|
||||||
$(MAKE) -C sbapp cleanrns
|
$(MAKE) -C sbapp cleanrns
|
||||||
|
|
||||||
build_wheel:
|
compile_wheel:
|
||||||
python3 setup.py sdist bdist_wheel
|
python3 setup.py bdist_wheel
|
||||||
|
|
||||||
|
build_wheel: remove_symlinks compile_wheel create_symlinks
|
||||||
|
|
||||||
build_win_exe:
|
build_win_exe:
|
||||||
python -m PyInstaller sideband.spec --noconfirm
|
python -m PyInstaller sideband.spec --noconfirm
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ Plyer
|
||||||
'''
|
'''
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'accelerometer', 'audio', 'barometer', 'battery', 'bluetooth',
|
'accelerometer', 'barometer', 'battery', 'bluetooth',
|
||||||
'brightness', 'call', 'camera', 'compass', 'cpu', 'email', 'filechooser',
|
'brightness', 'call', 'camera', 'compass', 'cpu', 'email', 'filechooser',
|
||||||
'flash', 'gps', 'gravity', 'gyroscope', 'humidity', 'irblaster',
|
'flash', 'gps', 'gravity', 'gyroscope', 'humidity', 'irblaster',
|
||||||
'keystore', 'light', 'notification', 'orientation', 'processors',
|
'keystore', 'light', 'notification', 'orientation', 'processors',
|
||||||
|
|
@ -29,9 +29,6 @@ accelerometer = Proxy('accelerometer', facades.Accelerometer)
|
||||||
#: Keyring proxy to :class::`plyer.facades.Keystore`
|
#: Keyring proxy to :class::`plyer.facades.Keystore`
|
||||||
keystore = Proxy('keystore', facades.Keystore)
|
keystore = Proxy('keystore', facades.Keystore)
|
||||||
|
|
||||||
#: Audio proxy to :class:`plyer.facades.Audio`
|
|
||||||
audio = Proxy('audio', facades.Audio)
|
|
||||||
|
|
||||||
#: Barometer proxy to :class:`plyer.facades.Barometer`
|
#: Barometer proxy to :class:`plyer.facades.Barometer`
|
||||||
barometer = Proxy('barometer', facades.Barometer)
|
barometer = Proxy('barometer', facades.Barometer)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ Interface of all the features available.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
__all__ = ('Accelerometer', 'Audio', 'Barometer', 'Battery', 'Call', 'Camera',
|
__all__ = ('Accelerometer', 'Barometer', 'Battery', 'Call', 'Camera',
|
||||||
'Compass', 'Email', 'FileChooser', 'GPS', 'Gravity', 'Gyroscope',
|
'Compass', 'Email', 'FileChooser', 'GPS', 'Gravity', 'Gyroscope',
|
||||||
'IrBlaster', 'Light', 'Orientation', 'Notification', 'Proximity',
|
'IrBlaster', 'Light', 'Orientation', 'Notification', 'Proximity',
|
||||||
'Sms', 'TTS', 'UniqueID', 'Vibrator', 'Wifi', 'Flash', 'CPU',
|
'Sms', 'TTS', 'UniqueID', 'Vibrator', 'Wifi', 'Flash', 'CPU',
|
||||||
|
|
@ -17,7 +17,6 @@ __all__ = ('Accelerometer', 'Audio', 'Barometer', 'Battery', 'Call', 'Camera',
|
||||||
import RNS
|
import RNS
|
||||||
if RNS.vendor.platformutils.is_android():
|
if RNS.vendor.platformutils.is_android():
|
||||||
from plyer.facades.accelerometer import Accelerometer
|
from plyer.facades.accelerometer import Accelerometer
|
||||||
from plyer.facades.audio import Audio
|
|
||||||
from plyer.facades.barometer import Barometer
|
from plyer.facades.barometer import Barometer
|
||||||
from plyer.facades.battery import Battery
|
from plyer.facades.battery import Battery
|
||||||
from plyer.facades.call import Call
|
from plyer.facades.call import Call
|
||||||
|
|
@ -53,7 +52,6 @@ if RNS.vendor.platformutils.is_android():
|
||||||
from plyer.facades.devicename import DeviceName
|
from plyer.facades.devicename import DeviceName
|
||||||
else:
|
else:
|
||||||
from sbapp.plyer.facades.accelerometer import Accelerometer
|
from sbapp.plyer.facades.accelerometer import Accelerometer
|
||||||
from sbapp.plyer.facades.audio import Audio
|
|
||||||
from sbapp.plyer.facades.barometer import Barometer
|
from sbapp.plyer.facades.barometer import Barometer
|
||||||
from sbapp.plyer.facades.battery import Battery
|
from sbapp.plyer.facades.battery import Battery
|
||||||
from sbapp.plyer.facades.call import Call
|
from sbapp.plyer.facades.call import Call
|
||||||
|
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
'''
|
|
||||||
Audio
|
|
||||||
=====
|
|
||||||
|
|
||||||
The :class:`Audio` is used for recording audio.
|
|
||||||
|
|
||||||
Default path for recording is set in platform implementation.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
On Android the `RECORD_AUDIO`, `WAKE_LOCK` permissions are needed.
|
|
||||||
|
|
||||||
Simple Examples
|
|
||||||
---------------
|
|
||||||
|
|
||||||
To get the file path::
|
|
||||||
|
|
||||||
>>> audio.file_path
|
|
||||||
'/sdcard/testrecorder.3gp'
|
|
||||||
|
|
||||||
To set the file path::
|
|
||||||
|
|
||||||
>>> import os
|
|
||||||
>>> current_list = os.listdir('.')
|
|
||||||
['/sdcard/testrecorder.3gp', '/sdcard/testrecorder1.3gp',
|
|
||||||
'/sdcard/testrecorder2.3gp', '/sdcard/testrecorder3.3gp']
|
|
||||||
>>> file_path = current_list[2]
|
|
||||||
>>> audio.file_path = file_path
|
|
||||||
|
|
||||||
To start recording::
|
|
||||||
|
|
||||||
>>> from plyer import audio
|
|
||||||
>>> audio.start()
|
|
||||||
|
|
||||||
To stop recording::
|
|
||||||
|
|
||||||
>>> audio.stop()
|
|
||||||
|
|
||||||
To play recording::
|
|
||||||
|
|
||||||
>>> audio.play()
|
|
||||||
|
|
||||||
Supported Platforms
|
|
||||||
-------------------
|
|
||||||
Android, Windows, macOS
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class Audio:
|
|
||||||
'''
|
|
||||||
Audio facade.
|
|
||||||
'''
|
|
||||||
|
|
||||||
state = 'ready'
|
|
||||||
_file_path = ''
|
|
||||||
|
|
||||||
def __init__(self, file_path=None):
|
|
||||||
super().__init__()
|
|
||||||
self._file_path = file_path or self._file_path
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
'''
|
|
||||||
Start record.
|
|
||||||
'''
|
|
||||||
self._start()
|
|
||||||
self.state = 'recording'
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
'''
|
|
||||||
Stop record.
|
|
||||||
'''
|
|
||||||
self._stop()
|
|
||||||
self.state = 'ready'
|
|
||||||
|
|
||||||
def play(self):
|
|
||||||
'''
|
|
||||||
Play current recording.
|
|
||||||
'''
|
|
||||||
self._play()
|
|
||||||
self.state = 'playing'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def file_path(self):
|
|
||||||
return self._file_path
|
|
||||||
|
|
||||||
@file_path.setter
|
|
||||||
def file_path(self, location):
|
|
||||||
'''
|
|
||||||
Location of the recording.
|
|
||||||
'''
|
|
||||||
assert isinstance(location, str), 'Location must be string or unicode'
|
|
||||||
self._file_path = location
|
|
||||||
|
|
||||||
# private
|
|
||||||
|
|
||||||
def _start(self):
|
|
||||||
raise IOError("JUICE")
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _stop(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _play(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
@ -1,108 +0,0 @@
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
from jnius import autoclass
|
|
||||||
|
|
||||||
from plyer.facades.audio import Audio
|
|
||||||
|
|
||||||
# Recorder Classes
|
|
||||||
MediaRecorder = autoclass('android.media.MediaRecorder')
|
|
||||||
AudioSource = autoclass('android.media.MediaRecorder$AudioSource')
|
|
||||||
OutputFormat = autoclass('android.media.MediaRecorder$OutputFormat')
|
|
||||||
AudioEncoder = autoclass('android.media.MediaRecorder$AudioEncoder')
|
|
||||||
|
|
||||||
# Player Classes
|
|
||||||
MediaPlayer = autoclass('android.media.MediaPlayer')
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidAudio(Audio):
|
|
||||||
'''Audio for android.
|
|
||||||
|
|
||||||
For recording audio we use MediaRecorder Android class.
|
|
||||||
For playing audio we use MediaPlayer Android class.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, file_path=None):
|
|
||||||
default_path = None
|
|
||||||
super().__init__(file_path or default_path)
|
|
||||||
|
|
||||||
self._recorder = None
|
|
||||||
self._player = None
|
|
||||||
self._check_thread = None
|
|
||||||
self._finished_callback = None
|
|
||||||
self._format = "opus"
|
|
||||||
self.is_playing = False
|
|
||||||
|
|
||||||
def _check_playback(self):
|
|
||||||
while self._player and self._player.isPlaying():
|
|
||||||
time.sleep(0.25)
|
|
||||||
|
|
||||||
self.is_playing = False
|
|
||||||
|
|
||||||
if self._finished_callback and callable(self._finished_callback):
|
|
||||||
self._check_thread = None
|
|
||||||
self._finished_callback(self)
|
|
||||||
|
|
||||||
|
|
||||||
def _start(self):
|
|
||||||
self._recorder = MediaRecorder()
|
|
||||||
if self._format == "aac":
|
|
||||||
self._recorder.setAudioSource(AudioSource.DEFAULT)
|
|
||||||
self._recorder.setAudioSamplingRate(48000)
|
|
||||||
self._recorder.setAudioEncodingBitRate(64000)
|
|
||||||
self._recorder.setAudioChannels(1)
|
|
||||||
self._recorder.setOutputFormat(OutputFormat.MPEG_4)
|
|
||||||
self._recorder.setAudioEncoder(AudioEncoder.AAC)
|
|
||||||
|
|
||||||
else:
|
|
||||||
self._recorder.setAudioSource(AudioSource.DEFAULT)
|
|
||||||
self._recorder.setAudioSamplingRate(48000)
|
|
||||||
self._recorder.setAudioEncodingBitRate(12000)
|
|
||||||
self._recorder.setAudioChannels(1)
|
|
||||||
self._recorder.setOutputFormat(OutputFormat.OGG)
|
|
||||||
self._recorder.setAudioEncoder(AudioEncoder.OPUS)
|
|
||||||
|
|
||||||
self._recorder.setOutputFile(self.file_path)
|
|
||||||
|
|
||||||
self._recorder.prepare()
|
|
||||||
self._recorder.start()
|
|
||||||
|
|
||||||
def _stop(self):
|
|
||||||
if self._recorder:
|
|
||||||
try:
|
|
||||||
self._recorder.stop()
|
|
||||||
self._recorder.release()
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not stop recording: "+str(e))
|
|
||||||
|
|
||||||
self._recorder = None
|
|
||||||
|
|
||||||
if self._player:
|
|
||||||
try:
|
|
||||||
self._player.stop()
|
|
||||||
self._player.release()
|
|
||||||
except Exception as e:
|
|
||||||
print("Could not stop playback: "+str(e))
|
|
||||||
|
|
||||||
self._player = None
|
|
||||||
|
|
||||||
self.is_playing = False
|
|
||||||
|
|
||||||
def _play(self):
|
|
||||||
self._player = MediaPlayer()
|
|
||||||
self._player.setDataSource(self.file_path)
|
|
||||||
self._player.prepare()
|
|
||||||
self._player.start()
|
|
||||||
self.is_playing = True
|
|
||||||
|
|
||||||
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
|
|
||||||
self._check_thread.start()
|
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
self._stop()
|
|
||||||
|
|
||||||
def playing(self):
|
|
||||||
return self.is_playing
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return AndroidAudio()
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
'''
|
|
||||||
iOS accelerometer
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
Taken from: http://pyobjus.readthedocs.org/en/latest/pyobjus_ios.html \
|
|
||||||
#accessing-accelerometer
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Accelerometer
|
|
||||||
from pyobjus import autoclass
|
|
||||||
|
|
||||||
|
|
||||||
class IosAccelerometer(Accelerometer):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.bridge = autoclass('bridge').alloc().init()
|
|
||||||
self.bridge.motionManager.setAccelerometerUpdateInterval_(0.1)
|
|
||||||
|
|
||||||
def _enable(self):
|
|
||||||
self.bridge.startAccelerometer()
|
|
||||||
|
|
||||||
def _disable(self):
|
|
||||||
self.bridge.stopAccelerometer()
|
|
||||||
|
|
||||||
def _get_acceleration(self):
|
|
||||||
return (
|
|
||||||
self.bridge.ac_x,
|
|
||||||
self.bridge.ac_y,
|
|
||||||
self.bridge.ac_z)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IosAccelerometer()
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
'''
|
|
||||||
iOS Barometer
|
|
||||||
-------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Barometer
|
|
||||||
from pyobjus import autoclass
|
|
||||||
|
|
||||||
|
|
||||||
class iOSBarometer(Barometer):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.bridge = autoclass('bridge').alloc().init()
|
|
||||||
|
|
||||||
def _enable(self):
|
|
||||||
self.bridge.startRelativeAltitude()
|
|
||||||
|
|
||||||
def _disable(self):
|
|
||||||
self.bridge.stopRelativeAltitude()
|
|
||||||
|
|
||||||
def _get_pressure(self):
|
|
||||||
'''
|
|
||||||
1 kPa = 10 hPa
|
|
||||||
'''
|
|
||||||
return (
|
|
||||||
self.bridge.pressure * 10)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return iOSBarometer()
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
'''
|
|
||||||
Module of iOS API for plyer.battery.
|
|
||||||
'''
|
|
||||||
|
|
||||||
from pyobjus import autoclass
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
from sbapp.plyer.facades import Battery
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/UIKit.framework')
|
|
||||||
UIDevice = autoclass('UIDevice')
|
|
||||||
|
|
||||||
|
|
||||||
class IOSBattery(Battery):
|
|
||||||
'''
|
|
||||||
Implementation of iOS battery API.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.device = UIDevice.currentDevice()
|
|
||||||
|
|
||||||
def _get_state(self):
|
|
||||||
status = {"isCharging": None, "percentage": None}
|
|
||||||
|
|
||||||
if not self.device.batteryMonitoringEnabled:
|
|
||||||
self.device.setBatteryMonitoringEnabled_(True)
|
|
||||||
|
|
||||||
if self.device.batteryState == 0:
|
|
||||||
is_charging = None
|
|
||||||
elif self.device.batteryState == 2:
|
|
||||||
is_charging = True
|
|
||||||
else:
|
|
||||||
is_charging = False
|
|
||||||
|
|
||||||
percentage = self.device.batteryLevel * 100.
|
|
||||||
|
|
||||||
status['isCharging'] = is_charging
|
|
||||||
status['percentage'] = percentage
|
|
||||||
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
'''
|
|
||||||
Instance for facade proxy.
|
|
||||||
'''
|
|
||||||
return IOSBattery()
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
'''
|
|
||||||
iOS Brightness
|
|
||||||
--------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from pyobjus import autoclass
|
|
||||||
from sbapp.plyer.facades import Brightness
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/UIKit.framework')
|
|
||||||
UIScreen = autoclass('UIScreen')
|
|
||||||
|
|
||||||
|
|
||||||
class iOSBrightness(Brightness):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.screen = UIScreen.mainScreen()
|
|
||||||
|
|
||||||
def _current_level(self):
|
|
||||||
return self.screen.brightness * 100
|
|
||||||
|
|
||||||
def set_level(self, level):
|
|
||||||
self.screen.brightness = level / 100
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return iOSBrightness()
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
'''
|
|
||||||
IOS Call
|
|
||||||
----------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Call
|
|
||||||
from pyobjus import autoclass, objc_str
|
|
||||||
|
|
||||||
NSURL = autoclass('NSURL')
|
|
||||||
NSString = autoclass('NSString')
|
|
||||||
UIApplication = autoclass('UIApplication')
|
|
||||||
|
|
||||||
|
|
||||||
class IOSCall(Call):
|
|
||||||
|
|
||||||
def _makecall(self, **kwargs):
|
|
||||||
tel = kwargs.get('tel')
|
|
||||||
url = "tel://" + tel
|
|
||||||
nsurl = NSURL.alloc().initWithString_(objc_str(url))
|
|
||||||
|
|
||||||
UIApplication.sharedApplication().openURL_(nsurl)
|
|
||||||
|
|
||||||
def _dialcall(self, **kwargs):
|
|
||||||
pass
|
|
||||||
# Not possible, Access not provided by iPhone SDK
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IOSCall()
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
from os import remove
|
|
||||||
from sbapp.plyer.facades import Camera
|
|
||||||
|
|
||||||
from sbapp.plyer.utils import reify
|
|
||||||
|
|
||||||
|
|
||||||
class iOSCamera(Camera):
|
|
||||||
|
|
||||||
@reify
|
|
||||||
def photos(self):
|
|
||||||
# pyPhotoLibrary is a ios recipe/module that
|
|
||||||
# interacts with the gallery and the camera on ios.
|
|
||||||
from photolibrary import PhotosLibrary
|
|
||||||
return PhotosLibrary()
|
|
||||||
|
|
||||||
def _take_picture(self, on_complete, filename=None):
|
|
||||||
assert on_complete is not None
|
|
||||||
self.on_complete = on_complete
|
|
||||||
self.filename = filename
|
|
||||||
photos = self.photos
|
|
||||||
|
|
||||||
if not photos.isCameraAvailable():
|
|
||||||
# no camera hardware
|
|
||||||
return False
|
|
||||||
|
|
||||||
photos.bind(on_image_captured=self.capture_callback)
|
|
||||||
self._capture_filename = filename
|
|
||||||
photos.capture_image(filename)
|
|
||||||
return True
|
|
||||||
|
|
||||||
def capture_callback(self, photolibrary):
|
|
||||||
# Image was chosen
|
|
||||||
|
|
||||||
# unbind
|
|
||||||
self.photos.unbind(on_image_captured=self.capture_callback)
|
|
||||||
|
|
||||||
if self.on_complete(self.filename):
|
|
||||||
self._remove(self.filename)
|
|
||||||
|
|
||||||
def _take_video(self, on_complete, filename=None):
|
|
||||||
assert on_complete is not None
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def _remove(self, fn):
|
|
||||||
try:
|
|
||||||
remove(fn)
|
|
||||||
except OSError:
|
|
||||||
print('Could not remove photo!')
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return iOSCamera()
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
'''
|
|
||||||
iOS Compass
|
|
||||||
-----------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Compass
|
|
||||||
from pyobjus import autoclass
|
|
||||||
|
|
||||||
|
|
||||||
class IosCompass(Compass):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.bridge = autoclass('bridge').alloc().init()
|
|
||||||
self.bridge.motionManager.setMagnetometerUpdateInterval_(0.1)
|
|
||||||
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
|
|
||||||
|
|
||||||
def _enable(self):
|
|
||||||
self.bridge.startMagnetometer()
|
|
||||||
self.bridge.startDeviceMotionWithReferenceFrame()
|
|
||||||
|
|
||||||
def _disable(self):
|
|
||||||
self.bridge.stopMagnetometer()
|
|
||||||
self.bridge.stopDeviceMotion()
|
|
||||||
|
|
||||||
def _get_orientation(self):
|
|
||||||
return (
|
|
||||||
self.bridge.mf_x,
|
|
||||||
self.bridge.mf_y,
|
|
||||||
self.bridge.mf_z)
|
|
||||||
|
|
||||||
def _get_field_uncalib(self):
|
|
||||||
return (
|
|
||||||
self.bridge.mg_x,
|
|
||||||
self.bridge.mg_y,
|
|
||||||
self.bridge.mg_z,
|
|
||||||
self.bridge.mg_x - self.bridge.mf_x,
|
|
||||||
self.bridge.mg_y - self.bridge.mf_y,
|
|
||||||
self.bridge.mg_z - self.bridge.mf_z)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IosCompass()
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
'''
|
|
||||||
Module of iOS API for plyer.email.
|
|
||||||
'''
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urllib.parse import quote
|
|
||||||
except ImportError:
|
|
||||||
from urllib import quote
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Email
|
|
||||||
from pyobjus import autoclass, objc_str
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/UIKit.framework')
|
|
||||||
|
|
||||||
NSURL = autoclass('NSURL')
|
|
||||||
NSString = autoclass('NSString')
|
|
||||||
UIApplication = autoclass('UIApplication')
|
|
||||||
|
|
||||||
|
|
||||||
class IOSEmail(Email):
|
|
||||||
'''
|
|
||||||
Implementation of iOS battery API.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def _send(self, **kwargs):
|
|
||||||
recipient = kwargs.get('recipient')
|
|
||||||
subject = kwargs.get('subject')
|
|
||||||
text = kwargs.get('text')
|
|
||||||
|
|
||||||
uri = "mailto:"
|
|
||||||
if recipient:
|
|
||||||
uri += str(recipient)
|
|
||||||
if subject:
|
|
||||||
uri += "?" if "?" not in uri else "&"
|
|
||||||
uri += "subject="
|
|
||||||
uri += quote(str(subject))
|
|
||||||
if text:
|
|
||||||
uri += "?" if "?" not in uri else "&"
|
|
||||||
uri += "body="
|
|
||||||
uri += quote(str(text))
|
|
||||||
|
|
||||||
nsurl = NSURL.alloc().initWithString_(objc_str(uri))
|
|
||||||
|
|
||||||
UIApplication.sharedApplication().openURL_(nsurl)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
'''
|
|
||||||
Instance for facade proxy.
|
|
||||||
'''
|
|
||||||
return IOSEmail()
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
'''
|
|
||||||
IOS file chooser
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
This module houses the iOS implementation of the plyer FileChooser.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.4
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import FileChooser
|
|
||||||
from pyobjus import autoclass, protocol
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/Photos.framework')
|
|
||||||
|
|
||||||
|
|
||||||
class IOSFileChooser(FileChooser):
|
|
||||||
'''
|
|
||||||
FileChooser implementation for IOS using
|
|
||||||
the built-in file browser via UIImagePickerController.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
self._on_selection = None
|
|
||||||
|
|
||||||
def _file_selection_dialog(self, *args, **kwargs):
|
|
||||||
"""
|
|
||||||
Function called when action is required, A "mode" parameter specifies
|
|
||||||
which and is one of "open", "save" or "dir".
|
|
||||||
"""
|
|
||||||
self._on_selection = kwargs["on_selection"]
|
|
||||||
if kwargs["mode"] == "open":
|
|
||||||
self._open()
|
|
||||||
else:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _get_picker(self):
|
|
||||||
"""
|
|
||||||
Return an instantiated and configured UIImagePickerController.
|
|
||||||
"""
|
|
||||||
picker = autoclass("UIImagePickerController")
|
|
||||||
po = picker.alloc().init()
|
|
||||||
po.sourceType = 0
|
|
||||||
po.delegate = self
|
|
||||||
return po
|
|
||||||
|
|
||||||
def _open(self):
|
|
||||||
"""
|
|
||||||
Launch the native iOS file browser. Upon selection, the
|
|
||||||
`imagePickerController_didFinishPickingMediaWithInfo_` delegate is
|
|
||||||
called where we close the file browser and handle the result.
|
|
||||||
"""
|
|
||||||
picker = self._get_picker()
|
|
||||||
UIApplication = autoclass('UIApplication')
|
|
||||||
vc = UIApplication.sharedApplication().keyWindow.rootViewController()
|
|
||||||
vc.presentViewController_animated_completion_(picker, True, None)
|
|
||||||
|
|
||||||
@protocol('UIImagePickerControllerDelegate')
|
|
||||||
def imagePickerController_didFinishPickingMediaWithInfo_(
|
|
||||||
self, image_picker, frozen_dict):
|
|
||||||
"""
|
|
||||||
Delegate which handles the result of the image selection process.
|
|
||||||
"""
|
|
||||||
image_picker.dismissViewControllerAnimated_completion_(True, None)
|
|
||||||
|
|
||||||
# Note: We need to call this Objective C class as there is currently
|
|
||||||
# no way to call a non-class function via pyobjus. And here,
|
|
||||||
# we have to use the `UIImagePNGRepresentation` to get the png
|
|
||||||
# representation. For this, please ensure you are using an
|
|
||||||
# appropriate version of kivy-ios.
|
|
||||||
native_image_picker = autoclass("NativeImagePicker").alloc().init()
|
|
||||||
path = native_image_picker.writeToPNG_(frozen_dict)
|
|
||||||
self._on_selection([path.UTF8String()])
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IOSFileChooser()
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
Flash
|
|
||||||
-----
|
|
||||||
"""
|
|
||||||
from sbapp.plyer.facades import Flash
|
|
||||||
from pyobjus import autoclass
|
|
||||||
|
|
||||||
NSString = autoclass("NSString")
|
|
||||||
AVCaptureDevice = autoclass("AVCaptureDevice")
|
|
||||||
AVMediaTypeVideo = NSString.alloc().initWithUTF8String_("vide")
|
|
||||||
AVCaptureTorchModeOff = 0
|
|
||||||
AVCaptureTorchModeOn = 1
|
|
||||||
|
|
||||||
|
|
||||||
class IosFlash(Flash):
|
|
||||||
_camera = None
|
|
||||||
|
|
||||||
def _on(self):
|
|
||||||
if self._camera is None:
|
|
||||||
self._camera_open()
|
|
||||||
if not self._camera:
|
|
||||||
return
|
|
||||||
self._camera.lockForConfiguration_(None)
|
|
||||||
try:
|
|
||||||
self._camera.setTorchMode(AVCaptureTorchModeOn)
|
|
||||||
finally:
|
|
||||||
self._camera.unlockForConfiguration()
|
|
||||||
|
|
||||||
def _off(self):
|
|
||||||
if not self._camera:
|
|
||||||
return
|
|
||||||
self._camera.lockForConfiguration_(None)
|
|
||||||
try:
|
|
||||||
self._camera.setTorchMode(AVCaptureTorchModeOff)
|
|
||||||
finally:
|
|
||||||
self._camera.unlockForConfiguration()
|
|
||||||
|
|
||||||
def _release(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _camera_open(self):
|
|
||||||
device = AVCaptureDevice.defaultDeviceWithMediaType_(AVMediaTypeVideo)
|
|
||||||
if not device:
|
|
||||||
return
|
|
||||||
self._camera = device
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IosFlash()
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
'''
|
|
||||||
iOS GPS
|
|
||||||
-----------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from pyobjus import autoclass, protocol
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
from sbapp.plyer.facades import GPS
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/CoreLocation.framework')
|
|
||||||
CLLocationManager = autoclass('CLLocationManager')
|
|
||||||
|
|
||||||
|
|
||||||
class IosGPS(GPS):
|
|
||||||
def _configure(self):
|
|
||||||
if not hasattr(self, '_location_manager'):
|
|
||||||
self._location_manager = CLLocationManager.alloc().init()
|
|
||||||
|
|
||||||
def _start(self, **kwargs):
|
|
||||||
self._location_manager.delegate = self
|
|
||||||
|
|
||||||
self._location_manager.requestWhenInUseAuthorization()
|
|
||||||
# NSLocationWhenInUseUsageDescription key must exist in Info.plist
|
|
||||||
# file. When the authorization prompt is displayed your app goes
|
|
||||||
# into pause mode and if your app doesn't support background mode
|
|
||||||
# it will crash.
|
|
||||||
self._location_manager.startUpdatingLocation()
|
|
||||||
|
|
||||||
def _stop(self):
|
|
||||||
self._location_manager.stopUpdatingLocation()
|
|
||||||
|
|
||||||
@protocol('CLLocationManagerDelegate')
|
|
||||||
def locationManager_didChangeAuthorizationStatus_(self, manager, status):
|
|
||||||
if self.on_status:
|
|
||||||
s_status = ''
|
|
||||||
provider_status = ''
|
|
||||||
provider = 'standard-ios-provider'
|
|
||||||
if status == 0:
|
|
||||||
provider_status = 'provider-disabled'
|
|
||||||
s_status = 'notDetermined'
|
|
||||||
elif status == 1:
|
|
||||||
provider_status = 'provider-enabled'
|
|
||||||
s_status = 'restricted'
|
|
||||||
elif status == 2:
|
|
||||||
provider_status = 'provider-disabled'
|
|
||||||
s_status = 'denied'
|
|
||||||
elif status == 3:
|
|
||||||
provider_status = 'provider-enabled'
|
|
||||||
s_status = 'authorizedAlways'
|
|
||||||
elif status == 4:
|
|
||||||
provider_status = 'provider-enabled'
|
|
||||||
s_status = 'authorizedWhenInUse'
|
|
||||||
self.on_status(provider_status, '{}: {}'.format(
|
|
||||||
provider, s_status))
|
|
||||||
|
|
||||||
@protocol('CLLocationManagerDelegate')
|
|
||||||
def locationManager_didUpdateLocations_(self, manager, locations):
|
|
||||||
location = manager.location
|
|
||||||
|
|
||||||
description = location.description.UTF8String()
|
|
||||||
split_description = description.split('<')[-1].split('>')[0].split(',')
|
|
||||||
|
|
||||||
lat, lon = [float(coord) for coord in split_description]
|
|
||||||
acc = float(description.split(' +/- ')[-1].split('m ')[0])
|
|
||||||
|
|
||||||
speed = location.speed
|
|
||||||
altitude = location.altitude
|
|
||||||
course = location.course
|
|
||||||
|
|
||||||
self.on_location(
|
|
||||||
lat=lat,
|
|
||||||
lon=lon,
|
|
||||||
speed=speed,
|
|
||||||
bearing=course,
|
|
||||||
altitude=altitude,
|
|
||||||
accuracy=acc)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IosGPS()
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
'''
|
|
||||||
iOS Gravity
|
|
||||||
-----------
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Gravity
|
|
||||||
from pyobjus import autoclass
|
|
||||||
|
|
||||||
|
|
||||||
class iOSGravity(Gravity):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.bridge = autoclass('bridge').alloc().init()
|
|
||||||
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
|
|
||||||
|
|
||||||
def _enable(self):
|
|
||||||
self.bridge.startDeviceMotion()
|
|
||||||
|
|
||||||
def _disable(self):
|
|
||||||
self.bridge.stopDeviceMotion()
|
|
||||||
|
|
||||||
def _get_gravity(self):
|
|
||||||
return (
|
|
||||||
self.bridge.g_x,
|
|
||||||
self.bridge.g_y,
|
|
||||||
self.bridge.g_z)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return iOSGravity()
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
'''
|
|
||||||
iOS Gyroscope
|
|
||||||
---------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Gyroscope
|
|
||||||
from pyobjus import autoclass
|
|
||||||
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/UIKit.framework')
|
|
||||||
UIDevice = autoclass('UIDevice')
|
|
||||||
|
|
||||||
device = UIDevice.currentDevice()
|
|
||||||
|
|
||||||
|
|
||||||
class IosGyroscope(Gyroscope):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.bridge = autoclass('bridge').alloc().init()
|
|
||||||
|
|
||||||
if int(device.systemVersion.UTF8String().split('.')[0]) <= 4:
|
|
||||||
self.bridge.motionManager.setGyroscopeUpdateInterval_(0.1)
|
|
||||||
else:
|
|
||||||
self.bridge.motionManager.setGyroUpdateInterval_(0.1)
|
|
||||||
|
|
||||||
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
|
|
||||||
|
|
||||||
def _enable(self):
|
|
||||||
self.bridge.startGyroscope()
|
|
||||||
self.bridge.startDeviceMotion()
|
|
||||||
|
|
||||||
def _disable(self):
|
|
||||||
self.bridge.stopGyroscope()
|
|
||||||
self.bridge.stopDeviceMotion()
|
|
||||||
|
|
||||||
def _get_orientation(self):
|
|
||||||
return (
|
|
||||||
self.bridge.rotation_rate_x,
|
|
||||||
self.bridge.rotation_rate_y,
|
|
||||||
self.bridge.rotation_rate_z)
|
|
||||||
|
|
||||||
def _get_rotation_uncalib(self):
|
|
||||||
return (
|
|
||||||
self.bridge.gy_x,
|
|
||||||
self.bridge.gy_y,
|
|
||||||
self.bridge.gy_z,
|
|
||||||
self.bridge.gy_x - self.bridge.rotation_rate_x,
|
|
||||||
self.bridge.gy_y - self.bridge.rotation_rate_y,
|
|
||||||
self.bridge.gy_z - self.bridge.rotation_rate_z)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IosGyroscope()
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
from sbapp.plyer.facades import Keystore
|
|
||||||
from pyobjus import autoclass, objc_str
|
|
||||||
|
|
||||||
NSUserDefaults = autoclass('NSUserDefaults')
|
|
||||||
|
|
||||||
|
|
||||||
class IosKeystore(Keystore):
|
|
||||||
|
|
||||||
def _set_key(self, servicename, key, value, **kwargs):
|
|
||||||
NSUserDefaults.standardUserDefaults().setObject_forKey_(
|
|
||||||
objc_str(value), objc_str(key))
|
|
||||||
|
|
||||||
def _get_key(self, servicename, key, **kwargs):
|
|
||||||
ret = NSUserDefaults.standardUserDefaults().stringForKey_(
|
|
||||||
objc_str(key))
|
|
||||||
if ret is not None:
|
|
||||||
return ret.UTF8String()
|
|
||||||
else:
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IosKeystore()
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
'''
|
|
||||||
Module of iOS API for plyer.maps.
|
|
||||||
'''
|
|
||||||
|
|
||||||
import webbrowser
|
|
||||||
from sbapp.plyer.facades import Maps
|
|
||||||
from urllib.parse import quote_plus
|
|
||||||
|
|
||||||
|
|
||||||
class iOSMaps(Maps):
|
|
||||||
'''
|
|
||||||
Implementation of iOS Maps API.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def _open_by_address(self, address, **kwargs):
|
|
||||||
'''
|
|
||||||
:param address: An address string that geolocation can understand.
|
|
||||||
'''
|
|
||||||
|
|
||||||
address = quote_plus(address, safe=',')
|
|
||||||
maps_address = 'http://maps.apple.com/?address=' + address
|
|
||||||
|
|
||||||
webbrowser.open(maps_address)
|
|
||||||
|
|
||||||
def _open_by_lat_long(self, latitude, longitude, **kwargs):
|
|
||||||
'''
|
|
||||||
Open a coordinate span denoting a latitudinal delta and a
|
|
||||||
longitudinal delta (similar to MKCoordinateSpan)
|
|
||||||
|
|
||||||
:param name: (optional), will set the name of the dropped pin
|
|
||||||
'''
|
|
||||||
|
|
||||||
name = kwargs.get("name", "Selected Location")
|
|
||||||
maps_address = 'http://maps.apple.com/?ll={},{}&q={}'.format(
|
|
||||||
latitude, longitude, name)
|
|
||||||
|
|
||||||
webbrowser.open(maps_address)
|
|
||||||
|
|
||||||
def _search(self, query, **kwargs):
|
|
||||||
'''
|
|
||||||
:param query: A string that describes the search object (ex. "Pizza")
|
|
||||||
|
|
||||||
:param latitude: (optional), narrow down query within area,
|
|
||||||
MUST BE USED WITH LONGITUDE
|
|
||||||
|
|
||||||
:param longitude: (optional), narrow down query within area,
|
|
||||||
MUST BE USED WITH LATITUDE
|
|
||||||
'''
|
|
||||||
|
|
||||||
latitude = kwargs.get('latitude')
|
|
||||||
longitude = kwargs.get('longitude')
|
|
||||||
|
|
||||||
query = quote_plus(query, safe=',')
|
|
||||||
maps_address = 'http://maps.apple.com/?q=' + query
|
|
||||||
|
|
||||||
if latitude is not None and longitude is not None:
|
|
||||||
maps_address += '&sll={},{}'.format(latitude, longitude)
|
|
||||||
|
|
||||||
webbrowser.open(maps_address)
|
|
||||||
|
|
||||||
def _route(self, saddr, daddr, **kwargs):
|
|
||||||
'''
|
|
||||||
:param saddr: can be given as 'address' or 'lat,long'
|
|
||||||
:param daddr: can be given as 'address' or 'lat,long'
|
|
||||||
'''
|
|
||||||
saddr = quote_plus(saddr, safe=',')
|
|
||||||
daddr = quote_plus(daddr, safe=',')
|
|
||||||
|
|
||||||
maps_address = 'http://maps.apple.com/?saddr={}&daddr={}'.format(
|
|
||||||
saddr, daddr)
|
|
||||||
webbrowser.open(maps_address)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
'''
|
|
||||||
Instance for facade proxy.
|
|
||||||
'''
|
|
||||||
return iOSMaps()
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
'''
|
|
||||||
IOS Sms
|
|
||||||
----------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Sms
|
|
||||||
from pyobjus import autoclass, objc_str
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
|
|
||||||
NSURL = autoclass('NSURL')
|
|
||||||
NSString = autoclass('NSString')
|
|
||||||
UIApplication = autoclass('UIApplication')
|
|
||||||
load_framework('/System/Library/Frameworks/MessageUI.framework')
|
|
||||||
|
|
||||||
|
|
||||||
class IOSSms(Sms):
|
|
||||||
|
|
||||||
def _send(self, **kwargs):
|
|
||||||
'''
|
|
||||||
This method provides sending messages to recipients.
|
|
||||||
|
|
||||||
Expects 2 parameters in kwargs:
|
|
||||||
- recipient: String type
|
|
||||||
- message: String type
|
|
||||||
|
|
||||||
Opens a message interface with recipient and message information.
|
|
||||||
'''
|
|
||||||
recipient = kwargs.get('recipient')
|
|
||||||
message = kwargs.get('message')
|
|
||||||
url = "sms:"
|
|
||||||
if recipient:
|
|
||||||
# Apple has not supported multiple recipients yet.
|
|
||||||
url += str(recipient)
|
|
||||||
if message:
|
|
||||||
# Apple has to supported it yet.
|
|
||||||
pass
|
|
||||||
|
|
||||||
nsurl = NSURL.alloc().initWithString_(objc_str(url))
|
|
||||||
UIApplication.sharedApplication().openURL_(nsurl)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return IOSSms()
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
'''
|
|
||||||
iOS Spatial Orientation
|
|
||||||
-----------------------
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import SpatialOrientation
|
|
||||||
from pyobjus import autoclass
|
|
||||||
|
|
||||||
|
|
||||||
class iOSSpatialOrientation(SpatialOrientation):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.bridge = autoclass('bridge').alloc().init()
|
|
||||||
self.bridge.motionManager.setDeviceMotionUpdateInterval_(0.1)
|
|
||||||
|
|
||||||
def _enable_listener(self):
|
|
||||||
self.bridge.startDeviceMotion()
|
|
||||||
|
|
||||||
def _disable_listener(self):
|
|
||||||
self.bridge.stopDeviceMotion()
|
|
||||||
|
|
||||||
def _get_orientation(self):
|
|
||||||
return (
|
|
||||||
self.bridge.sp_yaw,
|
|
||||||
self.bridge.sp_pitch,
|
|
||||||
self.bridge.sp_roll)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return iOSSpatialOrientation()
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
'''
|
|
||||||
iOS Storage Path
|
|
||||||
--------------------
|
|
||||||
'''
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import StoragePath
|
|
||||||
from pyobjus import autoclass
|
|
||||||
import os
|
|
||||||
|
|
||||||
NSFileManager = autoclass('NSFileManager')
|
|
||||||
|
|
||||||
# Directory constants (NSSearchPathDirectory enumeration)
|
|
||||||
NSApplicationDirectory = 1
|
|
||||||
NSDocumentDirectory = 9
|
|
||||||
NSDownloadsDirectory = 15
|
|
||||||
NSMoviesDirectory = 17
|
|
||||||
NSMusicDirectory = 18
|
|
||||||
NSPicturesDirectory = 19
|
|
||||||
|
|
||||||
|
|
||||||
class iOSStoragePath(StoragePath):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.defaultManager = NSFileManager.defaultManager()
|
|
||||||
|
|
||||||
def _get_home_dir(self):
|
|
||||||
return os.path.expanduser('~/')
|
|
||||||
|
|
||||||
def _get_external_storage_dir(self):
|
|
||||||
return 'This feature is not implemented for this platform.'
|
|
||||||
|
|
||||||
def _get_root_dir(self):
|
|
||||||
return 'This feature is not implemented for this platform.'
|
|
||||||
|
|
||||||
def _get_documents_dir(self):
|
|
||||||
return self.defaultManager.URLsForDirectory_inDomains_(
|
|
||||||
NSDocumentDirectory, 1).firstObject().absoluteString.UTF8String()
|
|
||||||
|
|
||||||
def _get_downloads_dir(self):
|
|
||||||
return self.defaultManager.URLsForDirectory_inDomains_(
|
|
||||||
NSDownloadsDirectory, 1).firstObject().absoluteString.UTF8String()
|
|
||||||
|
|
||||||
def _get_videos_dir(self):
|
|
||||||
return self.defaultManager.URLsForDirectory_inDomains_(
|
|
||||||
NSMoviesDirectory, 1).firstObject().absoluteString.UTF8String()
|
|
||||||
|
|
||||||
def _get_music_dir(self):
|
|
||||||
return self.defaultManager.URLsForDirectory_inDomains_(
|
|
||||||
NSMusicDirectory, 1).firstObject().absoluteString.UTF8String()
|
|
||||||
|
|
||||||
def _get_pictures_dir(self):
|
|
||||||
return self.defaultManager.URLsForDirectory_inDomains_(
|
|
||||||
NSPicturesDirectory, 1).firstObject().absoluteString.UTF8String()
|
|
||||||
|
|
||||||
def _get_application_dir(self):
|
|
||||||
return self.defaultManager.URLsForDirectory_inDomains_(
|
|
||||||
NSApplicationDirectory, 1).firstObject().absoluteString.\
|
|
||||||
UTF8String()
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return iOSStoragePath()
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
from pyobjus import autoclass, objc_str
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import TTS
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/AVFoundation.framework')
|
|
||||||
AVSpeechUtterance = autoclass('AVSpeechUtterance')
|
|
||||||
AVSpeechSynthesizer = autoclass('AVSpeechSynthesizer')
|
|
||||||
AVSpeechSynthesisVoice = autoclass('AVSpeechSynthesisVoice')
|
|
||||||
|
|
||||||
|
|
||||||
class iOSTextToSpeech(TTS):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.synth = AVSpeechSynthesizer.alloc().init()
|
|
||||||
self.voice = None
|
|
||||||
|
|
||||||
def _set_locale(self, locale="en-US"):
|
|
||||||
self.voice = AVSpeechSynthesisVoice.voiceWithLanguage_(
|
|
||||||
objc_str(locale)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _speak(self, **kwargs):
|
|
||||||
message = kwargs.get('message')
|
|
||||||
|
|
||||||
if not self.voice:
|
|
||||||
self._set_locale()
|
|
||||||
|
|
||||||
utterance = \
|
|
||||||
AVSpeechUtterance.speechUtteranceWithString_(objc_str(message))
|
|
||||||
|
|
||||||
utterance.voice = self.voice
|
|
||||||
self.synth.speakUtterance_(utterance)
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return iOSTextToSpeech()
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
'''
|
|
||||||
Module of iOS API for plyer.uniqueid.
|
|
||||||
'''
|
|
||||||
|
|
||||||
from pyobjus import autoclass
|
|
||||||
from pyobjus.dylib_manager import load_framework
|
|
||||||
from sbapp.plyer.facades import UniqueID
|
|
||||||
|
|
||||||
load_framework('/System/Library/Frameworks/UIKit.framework')
|
|
||||||
UIDevice = autoclass('UIDevice')
|
|
||||||
|
|
||||||
|
|
||||||
class IOSUniqueID(UniqueID):
|
|
||||||
'''
|
|
||||||
Implementation of iOS uniqueid API.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def _get_uid(self):
|
|
||||||
uuid = UIDevice.currentDevice().identifierForVendor.UUIDString()
|
|
||||||
return uuid.UTF8String()
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
'''
|
|
||||||
Instance for facade proxy.
|
|
||||||
'''
|
|
||||||
return IOSUniqueID()
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
'''Implementation Vibrator for iOS.
|
|
||||||
|
|
||||||
Install: Add AudioToolbox framework to your application.
|
|
||||||
'''
|
|
||||||
|
|
||||||
import ctypes
|
|
||||||
from sbapp.plyer.facades import Vibrator
|
|
||||||
|
|
||||||
|
|
||||||
class IosVibrator(Vibrator):
|
|
||||||
'''iOS Vibrator class.
|
|
||||||
|
|
||||||
iOS doesn't support any feature.
|
|
||||||
All time, pattern, repetition are ignored.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
try:
|
|
||||||
self._func = ctypes.CDLL(None).AudioServicesPlaySystemSound
|
|
||||||
except AttributeError:
|
|
||||||
self._func = None
|
|
||||||
|
|
||||||
def _vibrate(self, time=None, **kwargs):
|
|
||||||
# kSystemSoundID_Vibrate is 0x00000FFF
|
|
||||||
self._func(0xFFF)
|
|
||||||
|
|
||||||
def _pattern(self, pattern=None, repeat=None, **kwargs):
|
|
||||||
self._vibrate()
|
|
||||||
|
|
||||||
def _exists(self, **kwargs):
|
|
||||||
return self._func is not None
|
|
||||||
|
|
||||||
def _cancel(self, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
'''Returns Vibrator
|
|
||||||
|
|
||||||
:return: instance of class IosVibrator
|
|
||||||
'''
|
|
||||||
return IosVibrator()
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import RNS
|
|
||||||
import io
|
|
||||||
from sbapp.plyer.facades.audio import Audio
|
|
||||||
from ffpyplayer.player import MediaPlayer
|
|
||||||
from sbapp.pyogg import OpusFile, OpusBufferedEncoder, OggOpusWriter
|
|
||||||
import pyaudio
|
|
||||||
|
|
||||||
class LinuxAudio(Audio):
|
|
||||||
|
|
||||||
def __init__(self, file_path=None):
|
|
||||||
default_path = None
|
|
||||||
super().__init__(file_path or default_path)
|
|
||||||
|
|
||||||
self._recorder = None
|
|
||||||
self._player = None
|
|
||||||
self._check_thread = None
|
|
||||||
self._finished_callback = None
|
|
||||||
self._loaded_path = None
|
|
||||||
self.sound = None
|
|
||||||
self.pa = None
|
|
||||||
self.is_playing = False
|
|
||||||
self.recorder = None
|
|
||||||
self.should_record = False
|
|
||||||
|
|
||||||
def _check_playback(self):
|
|
||||||
run = True
|
|
||||||
while run and self.sound != None and not self.sound.get_pause():
|
|
||||||
time.sleep(0.25)
|
|
||||||
if self.duration:
|
|
||||||
pts = self.sound.get_pts()
|
|
||||||
if pts > self.duration:
|
|
||||||
run = False
|
|
||||||
|
|
||||||
self.is_playing = False
|
|
||||||
|
|
||||||
if self._finished_callback and callable(self._finished_callback):
|
|
||||||
self._check_thread = None
|
|
||||||
self._finished_callback(self)
|
|
||||||
|
|
||||||
def _record_job(self):
|
|
||||||
samples_per_second = self.default_rate;
|
|
||||||
bytes_per_sample = 2; frame_duration_ms = 20
|
|
||||||
opus_buffered_encoder = OpusBufferedEncoder()
|
|
||||||
opus_buffered_encoder.set_application("voip")
|
|
||||||
opus_buffered_encoder.set_sampling_frequency(samples_per_second)
|
|
||||||
opus_buffered_encoder.set_channels(1)
|
|
||||||
opus_buffered_encoder.set_frame_size(frame_duration_ms)
|
|
||||||
ogg_opus_writer = OggOpusWriter(self._file_path, opus_buffered_encoder)
|
|
||||||
|
|
||||||
frame_duration = frame_duration_ms/1000
|
|
||||||
frame_size = int(frame_duration * samples_per_second)
|
|
||||||
bytes_per_frame = frame_size*bytes_per_sample
|
|
||||||
|
|
||||||
read_bytes = 0
|
|
||||||
pcm_buf = b""
|
|
||||||
should_continue = True
|
|
||||||
while self.should_record and self.recorder:
|
|
||||||
samples_available = self.recorder.get_read_available()
|
|
||||||
bytes_available = samples_available*bytes_per_sample
|
|
||||||
if bytes_available > 0:
|
|
||||||
read_req = bytes_per_frame - len(pcm_buf)
|
|
||||||
read_n = min(bytes_available, read_req)
|
|
||||||
read_s = read_n//bytes_per_sample
|
|
||||||
rb = self.recorder.read(read_s); read_bytes += len(rb)
|
|
||||||
pcm_buf += rb
|
|
||||||
|
|
||||||
if len(pcm_buf) == bytes_per_frame:
|
|
||||||
ogg_opus_writer.write(memoryview(bytearray(pcm_buf)))
|
|
||||||
# RNS.log("Wrote frame of "+str(len(pcm_buf))+", expected size "+str(bytes_per_frame))
|
|
||||||
pcm_buf = b""
|
|
||||||
|
|
||||||
# Finish up anything left in buffer
|
|
||||||
time.sleep(frame_duration)
|
|
||||||
samples_available = self.recorder.get_read_available()
|
|
||||||
bytes_available = samples_available*bytes_per_sample
|
|
||||||
if bytes_available > 0:
|
|
||||||
read_req = bytes_per_frame - len(pcm_buf)
|
|
||||||
read_n = min(bytes_available, read_req)
|
|
||||||
read_s = read_n//bytes_per_sample
|
|
||||||
rb = self.recorder.read(read_s); read_bytes += len(rb)
|
|
||||||
pcm_buf += rb
|
|
||||||
|
|
||||||
if len(pcm_buf) == bytes_per_frame:
|
|
||||||
ogg_opus_writer.write(memoryview(bytearray(pcm_buf)))
|
|
||||||
# RNS.log("Wrote frame of "+str(len(pcm_buf))+", expected size "+str(bytes_per_frame))
|
|
||||||
pcm_buf = b""
|
|
||||||
|
|
||||||
ogg_opus_writer.close()
|
|
||||||
if self.recorder:
|
|
||||||
self.recorder.close()
|
|
||||||
|
|
||||||
def _start(self):
|
|
||||||
self.should_record = True
|
|
||||||
if self.pa == None:
|
|
||||||
self.pa = pyaudio.PyAudio()
|
|
||||||
self.default_input_device = self.pa.get_default_input_device_info()
|
|
||||||
self.default_rate = 48000
|
|
||||||
# self.default_rate = int(self.default_input_device["defaultSampleRate"])
|
|
||||||
if self.recorder:
|
|
||||||
self.recorder.close()
|
|
||||||
self.recorder = None
|
|
||||||
self.recorder = self.pa.open(self.default_rate, 1, pyaudio.paInt16, input=True)
|
|
||||||
threading.Thread(target=self._record_job, daemon=True).start()
|
|
||||||
|
|
||||||
def _stop(self):
|
|
||||||
if self.should_record == True:
|
|
||||||
self.should_record = False
|
|
||||||
|
|
||||||
elif self.sound != None:
|
|
||||||
self.sound.set_pause(True)
|
|
||||||
self.sound.seek(0, relative=False)
|
|
||||||
self.is_playing = False
|
|
||||||
|
|
||||||
def _play(self):
|
|
||||||
self.sound = MediaPlayer(self._file_path)
|
|
||||||
self.metadata = self.sound.get_metadata()
|
|
||||||
self.duration = self.metadata["duration"]
|
|
||||||
if self.duration == None:
|
|
||||||
time.sleep(0.15)
|
|
||||||
self.metadata = self.sound.get_metadata()
|
|
||||||
self.duration = self.metadata["duration"]
|
|
||||||
|
|
||||||
self._loaded_path = self._file_path
|
|
||||||
self.is_playing = True
|
|
||||||
|
|
||||||
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
|
|
||||||
self._check_thread.start()
|
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
self._loaded_path = None
|
|
||||||
|
|
||||||
def playing(self):
|
|
||||||
return self.is_playing
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return LinuxAudio()
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
from os.path import join
|
|
||||||
|
|
||||||
from pyobjus import autoclass
|
|
||||||
from pyobjus.dylib_manager import INCLUDE, load_framework
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Audio
|
|
||||||
from sbapp.plyer.platforms.macosx.storagepath import OSXStoragePath
|
|
||||||
|
|
||||||
import threading
|
|
||||||
|
|
||||||
load_framework(INCLUDE.Foundation)
|
|
||||||
load_framework(INCLUDE.AVFoundation)
|
|
||||||
|
|
||||||
AVAudioPlayer = autoclass("AVAudioPlayer")
|
|
||||||
AVAudioRecorder = autoclass("AVAudioRecorder")
|
|
||||||
AVAudioFormat = autoclass("AVAudioFormat")
|
|
||||||
NSString = autoclass('NSString')
|
|
||||||
NSURL = autoclass('NSURL')
|
|
||||||
NSError = autoclass('NSError').alloc()
|
|
||||||
|
|
||||||
|
|
||||||
class OSXAudio(Audio):
|
|
||||||
def __init__(self, file_path=None):
|
|
||||||
default_path = None
|
|
||||||
super().__init__(file_path or default_path)
|
|
||||||
|
|
||||||
self._recorder = None
|
|
||||||
self._player = None
|
|
||||||
self._current_file = None
|
|
||||||
|
|
||||||
self._check_thread = None
|
|
||||||
self._finished_callback = None
|
|
||||||
self._loaded_path = None
|
|
||||||
self.is_playing = False
|
|
||||||
self.sound = None
|
|
||||||
self.pa = None
|
|
||||||
self.is_playing = False
|
|
||||||
self.recorder = None
|
|
||||||
self.should_record = False
|
|
||||||
|
|
||||||
def _check_playback(self):
|
|
||||||
while self._player and self._player.isPlaying:
|
|
||||||
time.sleep(0.25)
|
|
||||||
|
|
||||||
if self._finished_callback and callable(self._finished_callback):
|
|
||||||
self._check_thread = None
|
|
||||||
self._finished_callback(self)
|
|
||||||
|
|
||||||
def _start(self):
|
|
||||||
# Conversion of Python file path string to Objective-C NSString
|
|
||||||
file_path_NSString = NSString.alloc()
|
|
||||||
file_path_NSString = file_path_NSString.initWithUTF8String_(
|
|
||||||
self._file_path
|
|
||||||
)
|
|
||||||
|
|
||||||
# Definition of Objective-C NSURL object for the output record file
|
|
||||||
# specified by NSString file path
|
|
||||||
file_NSURL = NSURL.alloc()
|
|
||||||
file_NSURL = file_NSURL.initWithString_(file_path_NSString)
|
|
||||||
|
|
||||||
# Internal audio file format specification
|
|
||||||
af = AVAudioFormat.alloc()
|
|
||||||
af = af.initWithCommonFormat_sampleRate_channels_interleaved_(
|
|
||||||
1, 44100.0, 1, True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Audio recorder instance initialization with specified file NSURL
|
|
||||||
# and audio file format
|
|
||||||
self._recorder = AVAudioRecorder.alloc()
|
|
||||||
self._recorder = self._recorder.initWithURL_format_error_(
|
|
||||||
file_NSURL, af, NSError
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self._recorder:
|
|
||||||
raise Exception(NSError.code, NSError.domain)
|
|
||||||
|
|
||||||
self._recorder.record()
|
|
||||||
|
|
||||||
# Setting the currently recorded file as current file
|
|
||||||
# for using it as a parameter in audio player
|
|
||||||
self._current_file = file_NSURL
|
|
||||||
|
|
||||||
def _stop(self):
|
|
||||||
if self._recorder:
|
|
||||||
self._recorder.stop()
|
|
||||||
self._recorder = None
|
|
||||||
|
|
||||||
if self._player:
|
|
||||||
self._player.stop()
|
|
||||||
self._player = None
|
|
||||||
|
|
||||||
def _play(self):
|
|
||||||
# Conversion of Python file path string to Objective-C NSString
|
|
||||||
file_path_NSString = NSString.alloc()
|
|
||||||
file_path_NSString = file_path_NSString.initWithUTF8String_(
|
|
||||||
self._file_path
|
|
||||||
)
|
|
||||||
|
|
||||||
# Definition of Objective-C NSURL object for the output record file
|
|
||||||
# specified by NSString file path
|
|
||||||
file_NSURL = NSURL.alloc()
|
|
||||||
file_NSURL = file_NSURL.initWithString_(file_path_NSString)
|
|
||||||
self._current_file = file_NSURL
|
|
||||||
|
|
||||||
# Audio player instance initialization with the file NSURL
|
|
||||||
# of the last recorded audio file
|
|
||||||
self._player = AVAudioPlayer.alloc()
|
|
||||||
self._player = self._player.initWithContentsOfURL_error_(
|
|
||||||
self._current_file, NSError
|
|
||||||
)
|
|
||||||
|
|
||||||
if not self._player:
|
|
||||||
raise Exception(NSError.code, NSError.domain)
|
|
||||||
|
|
||||||
self._player.play()
|
|
||||||
|
|
||||||
self._check_thread = threading.Thread(target=self._check_playback, daemon=True)
|
|
||||||
self._check_thread.start()
|
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
self._loaded_path = None
|
|
||||||
|
|
||||||
def playing(self):
|
|
||||||
return self.is_playing
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
return OSXAudio()
|
|
||||||
|
|
@ -1,413 +0,0 @@
|
||||||
'''
|
|
||||||
Documentation:
|
|
||||||
http://docs.microsoft.com/en-us/windows/desktop/Multimedia
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
from os.path import join
|
|
||||||
|
|
||||||
from ctypes import windll
|
|
||||||
from ctypes import (
|
|
||||||
sizeof, c_void_p, c_ulonglong, c_ulong,
|
|
||||||
c_wchar_p, byref, Structure, create_string_buffer
|
|
||||||
)
|
|
||||||
from ctypes.wintypes import DWORD, UINT
|
|
||||||
|
|
||||||
from sbapp.plyer.facades import Audio
|
|
||||||
from sbapp.plyer.platforms.win.storagepath import WinStoragePath
|
|
||||||
|
|
||||||
# DWORD_PTR i.e. ULONG_PTR, 32/64bit
|
|
||||||
ULONG_PTR = c_ulonglong if sizeof(c_void_p) == 8 else c_ulong
|
|
||||||
|
|
||||||
# device specific symbols
|
|
||||||
MCI_OPEN = 0x803
|
|
||||||
MCI_OPEN_TYPE = 0x2000
|
|
||||||
MCI_OPEN_ELEMENT = 512
|
|
||||||
MCI_RECORD = 0x80F
|
|
||||||
MCI_STOP = 0x808
|
|
||||||
MCI_SAVE = 0x813
|
|
||||||
MCI_PLAY = 0x806
|
|
||||||
MCI_CLOSE = 0x804
|
|
||||||
|
|
||||||
# recorder specific symbols
|
|
||||||
MCI_FROM = 4
|
|
||||||
MCI_TO = 8
|
|
||||||
MCI_WAIT = 2
|
|
||||||
MCI_SAVE_FILE = 256
|
|
||||||
|
|
||||||
|
|
||||||
class MCI_OPEN_PARMS(Structure):
|
|
||||||
'''
|
|
||||||
Struct for MCI_OPEN message parameters.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
_fields_ = [
|
|
||||||
('mciOpenParms', ULONG_PTR),
|
|
||||||
('wDeviceID', UINT),
|
|
||||||
('lpstrDeviceType', c_wchar_p),
|
|
||||||
('lpstrElementName', c_wchar_p),
|
|
||||||
('lpstrAlias', c_wchar_p)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MCI_RECORD_PARMS(Structure):
|
|
||||||
'''
|
|
||||||
Struct for MCI_RECORD message parameters.
|
|
||||||
|
|
||||||
http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-record-parms
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
_fields_ = [
|
|
||||||
('dwCallback', ULONG_PTR),
|
|
||||||
('dwFrom', DWORD),
|
|
||||||
('dwTo', DWORD)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MCI_SAVE_PARMS(Structure):
|
|
||||||
'''
|
|
||||||
Struct for MCI_SAVE message parameters.
|
|
||||||
|
|
||||||
http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-save-parms
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
_fields_ = [
|
|
||||||
('dwCallback', ULONG_PTR),
|
|
||||||
('lpfilename', c_wchar_p)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class MCI_PLAY_PARMS(Structure):
|
|
||||||
'''
|
|
||||||
Struct for MCI_PLAY message parameters.
|
|
||||||
|
|
||||||
http://docs.microsoft.com/en-us/windows/desktop/Multimedia/mci-play-parms
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
_fields_ = [
|
|
||||||
('dwCallback', ULONG_PTR),
|
|
||||||
('dwFrom', DWORD),
|
|
||||||
('dwTo', DWORD)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def send_command(device, msg, flags, params):
|
|
||||||
'''
|
|
||||||
Generic mciSendCommandW() wrapper with error handler.
|
|
||||||
All parameters are required as for mciSendCommandW().
|
|
||||||
In case of no `params` passed, use `None`, that value
|
|
||||||
won't be dereferenced.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
multimedia = windll.winmm
|
|
||||||
send_command_w = multimedia.mciSendCommandW
|
|
||||||
get_error = multimedia.mciGetErrorStringW
|
|
||||||
|
|
||||||
# error text buffer
|
|
||||||
# by API specification 128 is max, however the API sometimes
|
|
||||||
# kind of does not respect the documented bounds and returns
|
|
||||||
# more characters than buffer length...?!
|
|
||||||
error_len = 128
|
|
||||||
|
|
||||||
# big enough to prevent API accidentally segfaulting
|
|
||||||
error_text = create_string_buffer(error_len * 2)
|
|
||||||
|
|
||||||
# open a recording device with a new file
|
|
||||||
error_code = send_command_w(
|
|
||||||
device, # device ID
|
|
||||||
msg,
|
|
||||||
flags,
|
|
||||||
|
|
||||||
# reference to parameters structure or original value
|
|
||||||
# in case of params=False/0/None/...
|
|
||||||
byref(params) if params else params
|
|
||||||
)
|
|
||||||
|
|
||||||
# handle error messages if any
|
|
||||||
if error_code:
|
|
||||||
# device did not open, raise an exception
|
|
||||||
get_error(error_code, byref(error_text), error_len)
|
|
||||||
error_text = error_text.raw.replace(b'\x00', b'').decode('utf-8')
|
|
||||||
|
|
||||||
# either it can close already open device or it will fail because
|
|
||||||
# the device is in non-closable state, but the end result is the same
|
|
||||||
# and it makes no sense to parse MCI_CLOSE's error in this case
|
|
||||||
send_command_w(device, MCI_CLOSE, 0, None)
|
|
||||||
raise Exception(error_code, error_text)
|
|
||||||
|
|
||||||
# return params struct because some commands write into it
|
|
||||||
# to pass some values out of the local function scope
|
|
||||||
return params
|
|
||||||
|
|
||||||
|
|
||||||
class WinRecorder:
|
|
||||||
'''
|
|
||||||
Generic wrapper for MCI_RECORD handling the filenames and device closing
|
|
||||||
in the same approach like it is used for other platforms.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, device, filename):
|
|
||||||
self._device = device
|
|
||||||
self._filename = filename
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device(self):
|
|
||||||
'''
|
|
||||||
Public property returning device ID.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
return self._device
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filename(self):
|
|
||||||
'''
|
|
||||||
Public property returning filename for current recording.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
return self._filename
|
|
||||||
|
|
||||||
def record(self):
|
|
||||||
'''
|
|
||||||
Start recording a WAV sound.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
send_command(
|
|
||||||
device=self.device,
|
|
||||||
msg=MCI_RECORD,
|
|
||||||
flags=0,
|
|
||||||
params=None
|
|
||||||
)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
'''
|
|
||||||
Stop recording and save the data to a file path
|
|
||||||
self.filename. Wait until the file is written.
|
|
||||||
Close the device afterwards.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
# stop the recording first
|
|
||||||
send_command(
|
|
||||||
device=self.device,
|
|
||||||
msg=MCI_STOP,
|
|
||||||
flags=MCI_WAIT,
|
|
||||||
params=None
|
|
||||||
)
|
|
||||||
|
|
||||||
# choose filename for the WAV file
|
|
||||||
save_params = MCI_SAVE_PARMS()
|
|
||||||
save_params.lpfilename = self.filename
|
|
||||||
|
|
||||||
# save the sound data to a file and wait
|
|
||||||
# until it ends writing to the file
|
|
||||||
send_command(
|
|
||||||
device=self.device,
|
|
||||||
msg=MCI_SAVE,
|
|
||||||
flags=MCI_SAVE_FILE | MCI_WAIT,
|
|
||||||
params=save_params
|
|
||||||
)
|
|
||||||
|
|
||||||
# close the recording device
|
|
||||||
send_command(
|
|
||||||
device=self.device,
|
|
||||||
msg=MCI_CLOSE,
|
|
||||||
flags=0,
|
|
||||||
params=None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WinPlayer:
|
|
||||||
'''
|
|
||||||
Generic wrapper for MCI_PLAY handling the device closing.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, device):
|
|
||||||
self._device = device
|
|
||||||
|
|
||||||
@property
|
|
||||||
def device(self):
|
|
||||||
'''
|
|
||||||
Public property returning device ID.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
return self._device
|
|
||||||
|
|
||||||
def play(self):
|
|
||||||
'''
|
|
||||||
Start playing a WAV sound.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
play_params = MCI_PLAY_PARMS()
|
|
||||||
play_params.dwFrom = 0
|
|
||||||
|
|
||||||
send_command(
|
|
||||||
device=self.device,
|
|
||||||
msg=MCI_PLAY,
|
|
||||||
flags=MCI_FROM,
|
|
||||||
params=play_params
|
|
||||||
)
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
'''
|
|
||||||
Stop playing a WAV sound and close the device.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
send_command(
|
|
||||||
device=self.device,
|
|
||||||
msg=MCI_STOP,
|
|
||||||
flags=MCI_WAIT,
|
|
||||||
params=None
|
|
||||||
)
|
|
||||||
|
|
||||||
# close the playing device
|
|
||||||
send_command(
|
|
||||||
device=self.device,
|
|
||||||
msg=MCI_CLOSE,
|
|
||||||
flags=0,
|
|
||||||
params=None
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class WinAudio(Audio):
|
|
||||||
'''
|
|
||||||
Windows implementation of audio recording and audio playing.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, file_path=None):
|
|
||||||
# default path unless specified otherwise
|
|
||||||
default_path = join(
|
|
||||||
WinStoragePath().get_music_dir(),
|
|
||||||
'audio.wav'
|
|
||||||
)
|
|
||||||
super().__init__(file_path or default_path)
|
|
||||||
|
|
||||||
self._recorder = None
|
|
||||||
self._player = None
|
|
||||||
self._current_file = None
|
|
||||||
self._check_thread = None
|
|
||||||
self._finished_callback = None
|
|
||||||
self._loaded_path = None
|
|
||||||
self.is_playing = False
|
|
||||||
self.sound = None
|
|
||||||
self.pa = None
|
|
||||||
self.is_playing = False
|
|
||||||
self.recorder = None
|
|
||||||
self.should_record = False
|
|
||||||
|
|
||||||
def _start(self):
|
|
||||||
'''
|
|
||||||
Start recording a WAV sound in the background asynchronously.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
# clean everything before recording in case
|
|
||||||
# there is a different device open
|
|
||||||
self._stop()
|
|
||||||
|
|
||||||
# create structure and set device parameters
|
|
||||||
open_params = MCI_OPEN_PARMS()
|
|
||||||
open_params.lpstrDeviceType = 'waveaudio'
|
|
||||||
open_params.lpstrElementName = ''
|
|
||||||
|
|
||||||
# open a new device for recording
|
|
||||||
open_params = send_command(
|
|
||||||
device=0, # device ID before opening
|
|
||||||
msg=MCI_OPEN,
|
|
||||||
|
|
||||||
# empty filename in lpstrElementName
|
|
||||||
# device type in lpstrDeviceType
|
|
||||||
flags=MCI_OPEN_ELEMENT | MCI_OPEN_TYPE,
|
|
||||||
params=open_params
|
|
||||||
)
|
|
||||||
|
|
||||||
# get recorder with device id and path for saving
|
|
||||||
self._recorder = WinRecorder(
|
|
||||||
device=open_params.wDeviceID,
|
|
||||||
filename=self._file_path
|
|
||||||
)
|
|
||||||
self._recorder.record()
|
|
||||||
|
|
||||||
# Setting the currently recorded file as current file
|
|
||||||
# for using it as a parameter in audio player
|
|
||||||
self._current_file = self._recorder.filename
|
|
||||||
|
|
||||||
def _stop(self):
|
|
||||||
'''
|
|
||||||
Stop recording or playing of a WAV sound.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
if self._recorder:
|
|
||||||
self._recorder.stop()
|
|
||||||
self._recorder = None
|
|
||||||
|
|
||||||
if self._player:
|
|
||||||
self._player.stop()
|
|
||||||
self._player = None
|
|
||||||
|
|
||||||
def _play(self):
|
|
||||||
'''
|
|
||||||
Play a WAV sound from a file. Prioritize latest recorded file before
|
|
||||||
default file path from WinAudio.
|
|
||||||
|
|
||||||
.. versionadded:: 1.4.0
|
|
||||||
'''
|
|
||||||
|
|
||||||
# create structure and set device parameters
|
|
||||||
open_params = MCI_OPEN_PARMS()
|
|
||||||
open_params.lpstrDeviceType = 'waveaudio'
|
|
||||||
open_params.lpstrElementName = self._current_file or self._file_path
|
|
||||||
|
|
||||||
# open a new device for playing
|
|
||||||
open_params = send_command(
|
|
||||||
device=0, # device ID before opening
|
|
||||||
msg=MCI_OPEN,
|
|
||||||
|
|
||||||
# existing filename in lpstrElementName
|
|
||||||
# device type in lpstrDeviceType
|
|
||||||
flags=MCI_OPEN_ELEMENT | MCI_OPEN_TYPE,
|
|
||||||
params=open_params
|
|
||||||
)
|
|
||||||
|
|
||||||
# get recorder with device id and path for saving
|
|
||||||
self._player = WinPlayer(device=open_params.wDeviceID)
|
|
||||||
self._player.play()
|
|
||||||
|
|
||||||
def reload(self):
|
|
||||||
self._loaded_path = None
|
|
||||||
|
|
||||||
def playing(self):
|
|
||||||
return self.is_playing
|
|
||||||
|
|
||||||
|
|
||||||
def instance():
|
|
||||||
'''
|
|
||||||
Instance for facade proxy.
|
|
||||||
'''
|
|
||||||
return WinAudio()
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue