1 /*
2  * Copyright (C) 2021-2021 Teluu Inc. (http://www.teluu.com)
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18 
19 /* This is a simple Swift demo app that shows an example of how to use
20  * PJSUA API to make one audio+video call to another user.
21  */
22 
23 import SwiftUI
24 
25 class VidInfo: ObservableObject {
26     /* Video window */
27     @Published var vid_win:UIView? = nil
28 }
29 
30 class AppDelegate: NSObject, UIApplicationDelegate {
31     static let Shared = AppDelegate()
32     var vinfo = VidInfo()
33 }
34 
35 @main
36 struct ipjsua_swiftApp: App {
37     init() {
38         /* Create pjsua */
39         var status: pj_status_t;
40         status = pjsua_create();
41         if (status != PJ_SUCCESS.rawValue) {
42             NSLog("Failed creating pjsua");
43         }
44 
45         /* Init configs */
46         var cfg = pjsua_config();
47         var log_cfg = pjsua_logging_config();
48         var media_cfg = pjsua_media_config();
49         pjsua_config_default(&cfg);
50         pjsua_logging_config_default(&log_cfg);
51         pjsua_media_config_default(&media_cfg);
52 
53         /* Initialize application callbacks */
54         cfg.cb.on_call_state = on_call_state;
55         cfg.cb.on_call_media_state = on_call_media_state;
56 
57         /* Init pjsua */
58         status = pjsua_init(&cfg, &log_cfg, &media_cfg);
59 
60         /* Create transport */
61         var transport_id = pjsua_transport_id();
62         var tcp_cfg = pjsua_transport_config();
63         pjsua_transport_config_default(&tcp_cfg);
64         tcp_cfg.port = 5060;
65         status = pjsua_transport_create(PJSIP_TRANSPORT_TCP,
66                                         &tcp_cfg, &transport_id);
67 
68         /* Init account config */
69         let id = strdup("Test<sip:test@pjsip.org>");
70         let username = strdup("test");
71         let passwd = strdup("pwd");
72         let realm = strdup("*");
73         let registrar = strdup("sip:pjsip.org");
74         let proxy = strdup("sip:sip.pjsip.org;transport=tcp");
75 
76         var acc_cfg = pjsua_acc_config();
77         pjsua_acc_config_default(&acc_cfg);
78         acc_cfg.id = pj_str(id);
79         acc_cfg.cred_count = 1;
80         acc_cfg.cred_info.0.username = pj_str(username);
81         acc_cfg.cred_info.0.realm = pj_str(realm);
82         acc_cfg.cred_info.0.data = pj_str(passwd);
83         acc_cfg.reg_uri = pj_str(registrar);
84         acc_cfg.proxy_cnt = 1;
85         acc_cfg.proxy.0 = pj_str(proxy);
86         acc_cfg.vid_out_auto_transmit = pj_bool_t(PJ_TRUE.rawValue);
87         acc_cfg.vid_in_auto_show = pj_bool_t(PJ_TRUE.rawValue);
88 
89         /* Add account */
90         pjsua_acc_add(&acc_cfg, pj_bool_t(PJ_TRUE.rawValue), nil);
91 
92         /* Free strings */
93         free(id); free(username); free(passwd); free(realm);
94         free(registrar); free(proxy);
95 
96         /* Start pjsua */
97         status = pjsua_start();
98     }
99 
100     var body: some Scene {
101         WindowGroup {
102             ContentView()
103                 .environmentObject(AppDelegate.Shared.vinfo)
104                 .preferredColorScheme(.light)
105         }
106     }
107 }
108 
on_call_statenull109 private func on_call_state(call_id: pjsua_call_id, e: UnsafeMutablePointer<pjsip_event>?) {
110     var ci = pjsua_call_info();
111     pjsua_call_get_info(call_id, &ci);
112     if (ci.state == PJSIP_INV_STATE_DISCONNECTED) {
113         /* UIView update must be done in the main thread */
114         DispatchQueue.main.sync {
115             AppDelegate.Shared.vinfo.vid_win = nil;
116         }
117     }
118 }
119 
tupleToArray<Tuple, Value>null120 private func tupleToArray<Tuple, Value>(tuple: Tuple) -> [Value] {
121     let tupleMirror = Mirror(reflecting: tuple)
122     return tupleMirror.children.compactMap { (child: Mirror.Child) -> Value? in
123         return child.value as? Value
124     }
125 }
126 
on_call_media_statenull127 private func on_call_media_state(call_id: pjsua_call_id) {
128     var ci = pjsua_call_info();
129     pjsua_call_get_info(call_id, &ci);
130 
131     for mi in 0...ci.media_cnt {
132         let media: [pjsua_call_media_info] = tupleToArray(tuple: ci.media);
133 
134         if (media[Int(mi)].status == PJSUA_CALL_MEDIA_ACTIVE ||
135             media[Int(mi)].status == PJSUA_CALL_MEDIA_REMOTE_HOLD)
136         {
137             switch (media[Int(mi)].type) {
138             case PJMEDIA_TYPE_AUDIO:
139                 var call_conf_slot: pjsua_conf_port_id;
140 
141                 call_conf_slot = media[Int(mi)].stream.aud.conf_slot;
142                 pjsua_conf_connect(call_conf_slot, 0);
143                 pjsua_conf_connect(0, call_conf_slot);
144                 break;
145 
146             case PJMEDIA_TYPE_VIDEO:
147                 let wid = media[Int(mi)].stream.vid.win_in;
148                 var wi = pjsua_vid_win_info();
149 
150                 if (pjsua_vid_win_get_info(wid, &wi) == PJ_SUCCESS.rawValue) {
151                     let vid_win:UIView =
152                         Unmanaged<UIView>.fromOpaque(wi.hwnd.info.ios.window).takeUnretainedValue();
153 
154                     /* UIView update must be done in the main thread */
155                     DispatchQueue.main.sync {
156                         AppDelegate.Shared.vinfo.vid_win = vid_win;
157                     }
158                 }
159                 break;
160 
161             default:
162                 break;
163             }
164         }
165     }
166 }
167