Commit ee79fdb6 authored by Andreas Traczyk's avatar Andreas Traczyk Committed by Kateryna Kostiuk

video: add bridging, service, etc.

- builds the daemon with video support
- adds a bridging layer to expose daemon video functionality
- adds camera usage to the Info.plist and will request permission
  at app wizard screen(manual setting of camera permission is required
  if refused initially)
- sets hardware decoding to false
- sets video enabled to true for the current account

Change-Id: Id80fa8e439679dcccc08006d68af7d7b3350722d
Reviewed-by: Kateryna Kostiuk's avatarKateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
parent 7cd6e685
......@@ -238,6 +238,10 @@
62A88D371F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */; };
62A88D391F6C323500F8AB18 /* PresenceAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */; };
62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */; };
62AA15B21FF422810064A063 /* src in Resources */ = {isa = PBXBuildFile; fileRef = 62AA15B11FF422810064A063 /* src */; };
62AA15BF1FFC36840064A063 /* VideoAdapter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 62AA15BE1FFC36840064A063 /* VideoAdapter.mm */; };
62AA15C31FFC39C80064A063 /* VideoAdapterDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62AA15C21FFC39C80064A063 /* VideoAdapterDelegate.swift */; };
62AA15CA1FFD3D7E0064A063 /* VideoService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62AA15C91FFD3D7E0064A063 /* VideoService.swift */; };
62DFAB2C1F9FF030002D6F9C /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62DFAB2B1F9FF030002D6F9C /* Reachability.framework */; };
62DFAB2E1F9FF0D0002D6F9C /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62DFAB2D1F9FF0D0002D6F9C /* NetworkService.swift */; };
62E55B6D1F758E6F00D3FEF4 /* String+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E55B6C1F758E6F00D3FEF4 /* String+Helpers.swift */; };
......@@ -508,6 +512,11 @@
62A88D361F6C2ED400F8AB18 /* PresenceAdapterDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceAdapterDelegate.swift; sourceTree = "<group>"; };
62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PresenceAdapter.mm; sourceTree = "<group>"; };
62A88D3A1F6C3ACC00F8AB18 /* PresenceService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresenceService.swift; sourceTree = "<group>"; };
62AA15B11FF422810064A063 /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = ../../daemon/src; sourceTree = "<group>"; };
62AA15BD1FFC366D0064A063 /* VideoAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VideoAdapter.h; sourceTree = "<group>"; };
62AA15BE1FFC36840064A063 /* VideoAdapter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VideoAdapter.mm; sourceTree = "<group>"; };
62AA15C21FFC39C80064A063 /* VideoAdapterDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoAdapterDelegate.swift; sourceTree = "<group>"; };
62AA15C91FFD3D7E0064A063 /* VideoService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoService.swift; sourceTree = "<group>"; };
62AD0BFB1FE037BF00BEA1F6 /* ru-RU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ru-RU"; path = "ru-RU.lproj/Localizable.strings"; sourceTree = "<group>"; };
62AD0BFC1FE0384000BEA1F6 /* sq-AL */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "sq-AL"; path = "sq-AL.lproj/Localizable.strings"; sourceTree = "<group>"; };
62AD0BFF1FE0387F00BEA1F6 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/Localizable.strings; sourceTree = "<group>"; };
......@@ -721,6 +730,8 @@
0E48F9D21FDF150700D6CC08 /* ContactRequestManager.swift */,
0E49096B1FEAB225005CAA50 /* CallsAdapterDelegate.swift */,
0E49096D1FEAC0DE005CAA50 /* CallsService.swift */,
62AA15C21FFC39C80064A063 /* VideoAdapterDelegate.swift */,
62AA15C91FFD3D7E0064A063 /* VideoService.swift */,
);
path = Services;
sourceTree = "<group>";
......@@ -756,6 +767,8 @@
62A88D381F6C323500F8AB18 /* PresenceAdapter.mm */,
0E4909681FEAB156005CAA50 /* CallsAdapter.h */,
0E4909691FEAB156005CAA50 /* CallsAdapter.mm */,
62AA15BD1FFC366D0064A063 /* VideoAdapter.h */,
62AA15BE1FFC36840064A063 /* VideoAdapter.mm */,
);
path = Bridging;
sourceTree = "<group>";
......@@ -1640,10 +1653,12 @@
1A5DC02E1F3565640075E8EF /* ConversationViewModel.swift in Sources */,
1A2D189C1F264AD900B2C785 /* UIViewController+Ring.swift in Sources */,
02C9B63F1E1D4E8C00F82F0C /* ServiceEvent.swift in Sources */,
62AA15BF1FFC36840064A063 /* VideoAdapter.mm in Sources */,
1A2D18C11F29180700B2C785 /* AccountConfigModel.swift in Sources */,
1A3D28A71F0EB9DB00B524EE /* Bool+String.swift in Sources */,
5516C29F1E71CEFF009D3D2D /* AccountModelHelper.swift in Sources */,
1ABE07D31F0D8FE800D36361 /* Storyboards.swift in Sources */,
62AA15CA1FFD3D7E0064A063 /* VideoService.swift in Sources */,
0EDE34C71F868E1200FFA15C /* EditProfileViewController.swift in Sources */,
62A88D3B1F6C3ACC00F8AB18 /* PresenceService.swift in Sources */,
0E0FF1B51FC3947B003898C2 /* DBManager.swift in Sources */,
......@@ -1677,6 +1692,7 @@
1A2D18FF1F29352D00B2C785 /* MeViewModel.swift in Sources */,
62A88D391F6C323500F8AB18 /* PresenceAdapter.mm in Sources */,
1A2D18B71F29164700B2C785 /* SmartlistViewModel.swift in Sources */,
62AA15C31FFC39C80064A063 /* VideoAdapterDelegate.swift in Sources */,
04399AAE1D1C304300E99CD9 /* Utils.mm in Sources */,
56BBC9A31ED714DF00CDAF8B /* ConversationsService.swift in Sources */,
0E0FF1AF1FC38CBC003898C2 /* ProfileDataHelper.swift in Sources */,
......
......@@ -37,6 +37,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
private let contactsService = ContactsService(withContactsAdapter: ContactsAdapter())
private let presenceService = PresenceService(withPresenceAdapter: PresenceAdapter())
private let callService = CallsService(withCallsAdapter: CallsAdapter())
private let videoService = VideoService(withVideoAdapter: VideoAdapter())
private let networkService = NetworkService()
private var conversationManager: ConversationsManager?
private var contactRequestManager: ContactRequestManager?
......@@ -49,7 +50,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
withContactsService: self.contactsService,
withPresenceService: self.presenceService,
withNetworkService: self.networkService,
withCallService: self.callService)
withCallService: self.callService,
withVideoService: self.videoService)
}()
private lazy var appCoordinator: AppCoordinator = {
return AppCoordinator(with: self.injectionBag)
......@@ -74,6 +76,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
SystemAdapter().registerConfigurationHandler()
self.startDaemon()
// disables hardware decoding
self.videoService.setDecodingAccelerated(withState: false)
// requests permission to use the camera
// will enumerate and add devices once permission has been granted
self.videoService.setupInputs()
// start monitoring for network changes
self.networkService.monitorNetworkType()
// themetize the app
......@@ -87,15 +97,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
self.conversationManager = ConversationsManager(with: self.conversationsService, accountsService: self.accountService)
self.startDB()
self.accountService.loadAccounts().subscribe { [unowned self] (_) in
if let currentAccount = self.accountService.currentAccount {
self.contactsService.loadContacts(withAccount: currentAccount)
self.contactsService.loadContactRequests(withAccount: currentAccount)
self.presenceService.subscribeBuddies(withAccount: currentAccount, withContacts: self.contactsService.contacts.value)
if let ringID = AccountModelHelper(withAccount: currentAccount).ringId {
self.conversationManager?
.prepareConversationsForAccount(accountId: currentAccount.id, accountUri: ringID)
}
guard let currentAccount = self.accountService.currentAccount else {
self.log.error("Can't get current account!")
return
}
self.contactsService.loadContacts(withAccount: currentAccount)
self.contactsService.loadContactRequests(withAccount: currentAccount)
self.presenceService.subscribeBuddies(withAccount: currentAccount, withContacts: self.contactsService.contacts.value)
if let ringID = AccountModelHelper(withAccount: currentAccount).ringId {
self.conversationManager?
.prepareConversationsForAccount(accountId: currentAccount.id, accountUri: ringID)
}
// make sure video is enabled
let accountDetails = self.accountService.getAccountDetails(fromAccountId: currentAccount.id)
accountDetails.set(withConfigKeyModel: ConfigKeyModel(withKey: ConfigKey.videoEnabled), withValue: "true")
self.accountService.setAccountDetails(forAccountId: currentAccount.id, withDetails: accountDetails)
}.disposed(by: self.disposeBag)
self.window?.rootViewController = self.appCoordinator.rootViewController
......
......@@ -33,6 +33,7 @@
#import "Chameleon/Chameleon.h"
#import "ContactsAdapter.h"
#import "PresenceAdapter.h"
#import "VideoAdapter.h"
#import <CommonCrypto/CommonCrypto.h>
#import <Contacts/Contacts.h>
#import "CallsAdapter.h"
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Foundation/Foundation.h>
#import <AVFoundation/AVCaptureOutput.h>
@protocol VideoAdapterDelegate;
@interface VideoAdapter : NSObject
@property (class, nonatomic, weak) id <VideoAdapterDelegate> delegate;
- (void)addVideoDeviceWithName:(NSString*)deviceName withDevInfo:(NSDictionary*)deviceInfoDict;
- (void)registerSinkTargetWithSinkId:sinkId withWidth:(NSInteger)w withHeight:(NSInteger)h;
- (void)removeSinkTargetWithSinkId:(NSString*)sinkId;
- (void)writeOutgoingFrameWithImage:(UIImage*)image;
- (void)setDecodingAccelerated:(BOOL)state;
- (void)switchInput:(NSString*)deviceName;
@end
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "VideoAdapter.h"
#import "Utils.h"
#import "dring/videomanager_interface.h"
#import "Ring-Swift.h"
#include <pthread.h>
#include <functional>
#include <AVFoundation/AVFoundation.h>
#include <mutex>
using namespace DRing;
struct Renderer
{
std::mutex frameMutex;
std::condition_variable frameCv;
bool isRendering;
std::mutex renderMutex;
SinkTarget target;
SinkTarget::FrameBufferPtr daemonFramePtr_;
int width;
int height;
void bindSinkFunctions() {
target.pull = [this](std::size_t bytes) {
std::lock_guard<std::mutex> lk(renderMutex);
if (!daemonFramePtr_)
daemonFramePtr_.reset(new DRing::FrameBuffer);
daemonFramePtr_->storage.resize(bytes);
daemonFramePtr_->ptr = daemonFramePtr_->storage.data();
daemonFramePtr_->ptrSize = bytes;
return std::move(daemonFramePtr_);
};
target.push = [this](DRing::SinkTarget::FrameBufferPtr buf) {
std::lock_guard<std::mutex> lk(renderMutex);
daemonFramePtr_ = std::move(buf);
if(VideoAdapter.delegate) {
@autoreleasepool {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate((void *)daemonFramePtr_->ptr,
daemonFramePtr_->width,
daemonFramePtr_->height,
8,
4 * width,
colorSpace,
kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst);
CFRelease(colorSpace);
CGImageRef cgImage=CGBitmapContextCreateImage(bitmapContext);
CGContextRelease(bitmapContext);
UIImage* image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
isRendering = true;
[VideoAdapter.delegate writeFrameWithImage: image];
isRendering = false;
}
}
};
}
};
@implementation VideoAdapter {
std::map<std::string, std::shared_ptr<Renderer>> renderers;
}
// Static delegate that will receive the propagated daemon events
static id <VideoAdapterDelegate> _delegate;
#pragma mark Init
- (id)init {
if (self = [super init]) {
[self registerVideoHandlers];
}
return self;
}
#pragma mark -
#pragma mark Callbacks registration
- (void)registerVideoHandlers {
std::map<std::string, std::shared_ptr<CallbackWrapperBase>> videoHandlers;
videoHandlers.insert(exportable_callback<VideoSignal::DecodingStarted>([&](const std::string& renderer_id,
const std::string& shm_path,
int w,
int h,
bool is_mixer) {
if(VideoAdapter.delegate) {
NSString* rendererId = [NSString stringWithUTF8String:renderer_id.c_str()];;
[VideoAdapter.delegate decodingStartedWithRendererId:rendererId withWidth:(NSInteger)w withHeight:(NSInteger)h];
}
}));
videoHandlers.insert(exportable_callback<VideoSignal::DecodingStopped>([&](const std::string& renderer_id,
const std::string& shm_path,
bool is_mixer) {
if(VideoAdapter.delegate) {
NSString* rendererId = [NSString stringWithUTF8String:renderer_id.c_str()];
[VideoAdapter.delegate decodingStoppedWithRendererId:rendererId];
}
}));
videoHandlers.insert(exportable_callback<VideoSignal::StartCapture>([&](const std::string& device) {
if(VideoAdapter.delegate) {
NSString* deviceString = [NSString stringWithUTF8String:device.c_str()];
[VideoAdapter.delegate startCaptureWithDevice:deviceString];
}
}));
videoHandlers.insert(exportable_callback<VideoSignal::StopCapture>([&]() {
if(VideoAdapter.delegate) {
[VideoAdapter.delegate stopCapture];
}
}));
registerVideoHandlers(videoHandlers);
}
#pragma mark -
- (void)registerSinkTargetWithSinkId:sinkId withWidth:(NSInteger)w withHeight:(NSInteger)h {
auto _sinkId = std::string([sinkId UTF8String]);
auto renderer = std::make_shared<Renderer>();
renderer->width = static_cast<int>(w);
renderer->height = static_cast<int>(h);
renderer->bindSinkFunctions();
DRing::registerSinkTarget(_sinkId, renderer->target);
renderers.insert(std::make_pair(_sinkId, renderer));
}
- (void)removeSinkTargetWithSinkId:(NSString*)sinkId {
auto renderer = renderers.find(std::string([sinkId UTF8String]));
if (renderer != renderers.end()) {
std::unique_lock<std::mutex> lk(renderer->second->renderMutex);
renderer->second->frameCv.wait(lk, [=] {
return !renderer->second->isRendering;
});
renderers.erase(renderer);
}
}
- (void)writeOutgoingFrameWithImage:(UIImage*)image {
unsigned capacity = 4 * image.size.width * image.size.height;
uint8_t* buf = (uint8_t*)DRing::obtainFrame(capacity);
if (buf){
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGImageRef imageRef = [image CGImage];
CGContextRef bitmap = CGBitmapContextCreate(buf,
image.size.width,
image.size.height,
8,
image.size.width * 4,
colorSpace,
kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGContextDrawImage( bitmap, CGRectMake(0, 0, image.size.width, image.size.height), imageRef);
CGContextRelease(bitmap);
CGColorSpaceRelease(colorSpace);
}
DRing::releaseFrame((void*)buf);
}
- (void)addVideoDeviceWithName:(NSString*)deviceName withDevInfo:(NSDictionary*)deviceInfoDict {
std::vector<std::map<std::string, std::string>> devInfo;
auto setting = [Utils dictionnaryToMap:deviceInfoDict];
devInfo.emplace_back(setting);
DRing::addVideoDevice(std::string([deviceName UTF8String]), &devInfo);
DRing::setDefaultDevice(std::string([deviceName UTF8String]));
}
- (void)setDecodingAccelerated:(BOOL)state {
DRing::setDecodingAccelerated(state);
}
- (void)switchInput:(NSString*)deviceName {
DRing::switchInput(std::string([deviceName UTF8String]));
}
#pragma mark PresenceAdapterDelegate
+ (id <VideoAdapterDelegate>)delegate {
return _delegate;
}
+ (void) setDelegate:(id<VideoAdapterDelegate>)delegate {
_delegate = delegate;
}
#pragma mark -
@end
......@@ -26,7 +26,21 @@
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="cOr-ft-BIO">
<rect key="frame" x="0.0" y="0.0" width="375" height="618"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="DMu-Or-dd7">
<rect key="frame" x="0.0" y="309" width="375" height="0.0"/>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" id="GKH-fc-lLl"/>
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="DMu-Or-dd7" secondAttribute="trailing" id="5Aa-Zq-Wcw"/>
<constraint firstItem="DMu-Or-dd7" firstAttribute="centerY" secondItem="cOr-ft-BIO" secondAttribute="centerY" id="YVb-yp-TQG"/>
<constraint firstItem="DMu-Or-dd7" firstAttribute="leading" secondItem="cOr-ft-BIO" secondAttribute="leading" id="hFx-0M-Kk5"/>
</constraints>
</view>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<blurEffect style="light"/>
......@@ -107,6 +121,7 @@
<connections>
<outlet property="cancelButton" destination="B3b-V0-rjx" id="dU9-MG-0y3"/>
<outlet property="durationLabel" destination="zMN-6z-uXT" id="Uuf-ph-lrC"/>
<outlet property="incomingVideo" destination="DMu-Or-dd7" id="ogh-ft-54u"/>
<outlet property="infoBottomLabel" destination="SdV-jx-Mla" id="yX9-em-p4w"/>
<outlet property="nameLabel" destination="73Y-N1-Yga" id="XcQ-V6-ZrF"/>
<outlet property="profileImageView" destination="fnt-PQ-Q6P" id="MgB-Ev-bTc"/>
......
......@@ -23,6 +23,7 @@ import UIKit
import Chameleon
import RxSwift
import Reusable
import SwiftyBeaver
class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
......@@ -31,11 +32,15 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
@IBOutlet weak var durationLabel: UILabel!
@IBOutlet weak var infoBottomLabel: UILabel!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var incomingVideo: UIImageView!
@IBOutlet weak var capturedVideo: UIImageView!
var viewModel: CallViewModel!
fileprivate let disposeBag = DisposeBag()
private let log = SwiftyBeaver.self
override func viewDidLoad() {
super.viewDidLoad()
self.setupUI()
......
......@@ -34,9 +34,21 @@ class CallViewModel: Stateable, ViewModel {
fileprivate let callService: CallsService
fileprivate let contactsService: ContactsService
fileprivate let accountService: AccountsService
fileprivate let videoService: VideoService
private let disposeBag = DisposeBag()
fileprivate let log = SwiftyBeaver.self
lazy var incomingFrame: Observable<UIImage?> = {
return videoService.incomingVideoFrame.asObservable().map({ frame in
return frame
})
}()
lazy var capturedFrame: Observable<UIImage?> = {
return videoService.capturedVideoFrame.asObservable().map({ frame in
return frame
})
}()
var call: CallModel? {
didSet {
guard let call = self.call else {
......@@ -127,6 +139,7 @@ class CallViewModel: Stateable, ViewModel {
self.callService = injectionBag.callService
self.contactsService = injectionBag.contactsService
self.accountService = injectionBag.accountService
self.videoService = injectionBag.videoService
}
static func formattedDurationFrom(interval: Int) -> String {
let seconds = interval % 60
......
......@@ -31,6 +31,7 @@ class InjectionBag {
let presenceService: PresenceService
let networkService: NetworkService
let callService: CallsService
let videoService: VideoService
init (withDaemonService daemonService: DaemonService,
withAccountService accountService: AccountsService,
......@@ -39,7 +40,8 @@ class InjectionBag {
withContactsService contactsService: ContactsService,
withPresenceService presenceService: PresenceService,
withNetworkService networkService: NetworkService,
withCallService callService: CallsService) {
withCallService callService: CallsService,
withVideoService videoService: VideoService) {
self.daemonService = daemonService
self.accountService = accountService
self.nameService = nameService
......@@ -48,6 +50,7 @@ class InjectionBag {
self.presenceService = presenceService
self.networkService = networkService
self.callService = callService
self.videoService = videoService
}
}
......@@ -23,7 +23,7 @@
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>Used to take user profile picture</string>
<string>Used to take user profile picture and record video</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Used to let the user choose a profile picture</string>
<key>UILaunchStoryboardName</key>
......
......@@ -121,4 +121,26 @@ class AccountConfigModel: Object {
let value: String? = self.configValues[configKeyModel]
return value != nil ? value! : ""
}
/**
Getter for the configuration elements.
- Parameter configKeyModel: the ConfigKeyModel identifying the configuration element to get
- Parameter value: the string value to set at the key
*/
func set(withConfigKeyModel configKeyModel: ConfigKeyModel, withValue value: String) {
self.configValues[configKeyModel] = value
}
}
extension AccountConfigModel {
func toDetails() -> [String: String]? {
var details = [String: String]()
for (key, value) in self.configValues {
details[key.key.rawValue] = value
}
return details
}
}
......@@ -65,7 +65,9 @@ class AccountsService: AccountAdapterDelegate {
- SeeAlso: `sharedResponseStream`
*/
fileprivate let responseStream = PublishSubject<ServiceEvent>()
let dbManager = DBManager(profileHepler: ProfileDataHelper(),conversationHelper: ConversationDataHelper(),interactionHepler: InteractionDataHelper())
let dbManager = DBManager(profileHepler: ProfileDataHelper(),
conversationHelper: ConversationDataHelper(),
interactionHepler: InteractionDataHelper())
// MARK: - Public members
/**
......@@ -120,6 +122,7 @@ class AccountsService: AccountAdapterDelegate {
}
}
}
init(withAccountAdapter accountAdapter: AccountAdapter) {
self.accountList = []
......@@ -201,11 +204,10 @@ class AccountsService: AccountAdapterDelegate {
let devices = getKnownRingDevices(fromAccountId: accountId!)
account = try AccountModel(withAccountId: accountId!,
details: details,
volatileDetails: volatileDetails,
credentials: credentials,
devices: devices)
//TODO: set registration state as ready for a SIP account
details: details,
volatileDetails: volatileDetails,
credentials: credentials,
devices: devices)
let accountModelHelper = AccountModelHelper(withAccount: account!)
var accountAddedEvent = ServiceEvent(withEventType: .accountAdded)
......@@ -286,7 +288,7 @@ class AccountsService: AccountAdapterDelegate {
- Parameter id: the id of the account.
- Returns: the details of the accounts.
- Returns: the details of the account.
*/
func getAccountDetails(fromAccountId id: String) -> AccountConfigModel {
let details: NSDictionary = accountAdapter.getAccountDetails(id) as NSDictionary
......@@ -295,12 +297,22 @@ class AccountsService: AccountAdapterDelegate {
return accountDetails
}
/**
Sets all the details of an account in the daemon.
- Parameter id: the id of the account.
- Parameter newDetails: the new details to set for the account.
*/
func setAccountDetails(forAccountId id: String, withDetails newDetails: AccountConfigModel) {
let details = newDetails.toDetails()
accountAdapter.setAccountDetails(id, details: details)
}
/**
Gets all the volatile details of an account from the daemon.
- Parameter id: the id of the account.
- Returns: the volatile details of the accounts.
- Returns: the volatile details of the account.
*/
func getVolatileAccountDetails(fromAccountId id: String) -> AccountConfigModel {
let details: NSDictionary = accountAdapter.getVolatileAccountDetails(id) as NSDictionary
......@@ -372,7 +384,6 @@ class AccountsService: AccountAdapterDelegate {
if accountDetails == nil {
throw AddAccountError.templateNotConform
}
accountDetails!.updateValue("false", forKey: ConfigKey.videoEnabled.rawValue)
accountDetails!.updateValue("sipinfo", forKey: ConfigKey.accountDTMFType.rawValue)
return accountDetails!
}
......@@ -388,6 +399,7 @@ class AccountsService: AccountAdapterDelegate {
defaultDetails.updateValue("Ring", forKey: ConfigKey.accountAlias.rawValue)
defaultDetails.updateValue("bootstrap.ring.cx", forKey: ConfigKey.accountHostname.rawValue)
defaultDetails.updateValue("true", forKey: ConfigKey.accountUpnpEnabled.rawValue)
defaultDetails.updateValue("true", forKey: ConfigKey.videoEnabled.rawValue)
return defaultDetails
} catch {
throw error
......
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
@objc protocol VideoAdapterDelegate {
func decodingStarted(withRendererId rendererId: String, withWidth width: Int, withHeight height: Int)
func decodingStopped(withRendererId rendererId: String)
func startCapture(withDevice device: String)
func stopCapture()
func writeFrame(withImage image: UIImage?)
func setDecodingAccelerated(withState state: Bool)
func switchInput(toDevice device: String)
}
/*
* Copyright (C) 2018 Savoir-faire Linux Inc.
*
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import Foundation
import SwiftyBeaver
import RxSwift
import UIKit
import AVFoundation
typealias DeviceInfo = [String: String]
enum VideoError: Error {
case getPermissionFailed
case needPermission
case selectDeviceFailed
case setupInputDeviceFailed
case setupOutputDeviceFailed
case getConnectionFailed
case unsupportedParameter
case startCaptureFailed
case switchCameraFailed
}
protocol FrameExtractorDelegate: class {
func captured(image: UIImage)
}
class FrameExtractor: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
private let log = SwiftyBeaver.self
private let quality = AVCaptureSession.Preset.medium
var permissionGranted = Variable<Bool>(false)
lazy var permissionGrantedObservable: Observable<Bool> = {
return self.permissionGranted.asObservable()
}()
private let sessionQueue = DispatchQueue(label: "session queue")
private let captureSession = AVCaptureSession()
private let context = CIContext()
weak var delegate: FrameExtractorDelegate?
override init() {
super.init()
}
func getDeviceInfo(forPosition position: AVCaptureDevice.Position) throws -> DeviceInfo {
guard self.permissionGranted.value else {
throw VideoError.needPermission
}
self.captureSession.sessionPreset = self.quality
guard let captureDevice = self.selectCaptureDevice(withPosition: position) else {
throw VideoError.selectDeviceFailed
}