Skip to content

Commit

Permalink
Xamarin samples for Android and iOS (#3584)
Browse files Browse the repository at this point in the history
  • Loading branch information
trengginas authored Jun 16, 2023
1 parent 0e89bbe commit 2c56bdc
Show file tree
Hide file tree
Showing 71 changed files with 32,256 additions and 5 deletions.
18 changes: 15 additions & 3 deletions pjsip-apps/src/swig/csharp/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ NAMESPACE=$(PROJ_NAME).pjsua2
ARCH=$(TARGET_ARCH)

ifeq ($(OS),android)
LIBPJSUA2_DIR=$(PROJ_NAME)/Droid/lib/$(ARCH)
LIBPJSUA2_DIR=$(PROJ_NAME)/$(PROJ_NAME).Android/lib/$(ARCH)
LIBPJSUA2=$(LIBPJSUA2_DIR)/libpjsua2.so
SUP_CLASS_DIR=$(PROJ_NAME)/$(PROJ_NAME).Android/org/pjsip
else
ifeq ($(OS),ios)
LIBPJSUA2_DIR=$(PROJ_NAME)/iOS/lib/$(ARCH)
LIBPJSUA2_DIR=$(PROJ_NAME)/$(PROJ_NAME).iOS/lib/$(ARCH)
SWIG_FLAGS += -dllimport "__Internal"
else
LIBPJSUA2_DIR=$(PROJ_NAME)/lib
endif
Expand All @@ -47,13 +49,22 @@ all: $(LIBPJSUA2) sample
$(LIBPJSUA2): $(OUT_DIR)/pjsua2_wrap.o
mkdir -p $(LIBPJSUA2_DIR)
ifeq ($(OS),android)
mkdir -p $(SUP_CLASS_DIR)
$(PJ_CXX) -shared -o $(LIBPJSUA2) $(OUT_DIR)/pjsua2_wrap.o \
$(MY_CFLAGS) $(MY_LDFLAGS)
# copy libc++_shared.so manually
cp -f ${STD_CPP_LIB} $(LIBPJSUA2_DIR)
else
$(AR) $(LIBPJSUA2) $(AR_FLAGS) $(OUT_DIR)/pjsua2_wrap.o $(PJ_LIBXX_FILES)
endif
ifneq (,$(findstring PJMEDIA_VIDEO_DEV_HAS_ANDROID=1,$(ANDROID_CFLAGS)))
@echo "Copying Android camera helper components..."
cp $(PJDIR)/pjmedia/src/pjmedia-videodev/android/PjCamera*.java $(SUP_CLASS_DIR)/
endif
ifneq (,$(findstring PJMEDIA_AUDIO_DEV_HAS_OBOE=1,$(OBOE_CFLAGS)))
@echo "Copying Android Oboe audio device helper components..."
cp $(PJDIR)/pjmedia/src/pjmedia-audiodev/android/PjAudioDevInfo.java $(SUP_CLASS_DIR)/
endif

$(OUT_DIR)/pjsua2_wrap.o: $(OUT_DIR)/pjsua2_wrap.cpp
$(PJ_CXX) -c $(OUT_DIR)/pjsua2_wrap.cpp -o $(OUT_DIR)/pjsua2_wrap.o \
Expand All @@ -62,14 +73,15 @@ $(OUT_DIR)/pjsua2_wrap.o: $(OUT_DIR)/pjsua2_wrap.cpp
$(OUT_DIR)/pjsua2_wrap.cpp: ../pjsua2.i ../symbols.i Makefile $(SRCS)
mkdir -p $(OUT_DIR)
swig $(SWIG_FLAGS) -namespace $(NAMESPACE) -csharp -o $(OUT_DIR)/pjsua2_wrap.cpp ../pjsua2.i

sample: sample.cs
@echo "Copying sample code..."
cp sample.cs $(PROJ_NAME)/$(PROJ_NAME)

clean distclean realclean:
rm -rf ./$(OUT_DIR)/*
rm -rf ./$(LIBPJSUA2_DIR)/*
rm -rf ./$(SUP_CLASS_DIR)/*.java

install:

Expand Down
6 changes: 6 additions & 0 deletions pjsip-apps/src/swig/csharp/pjsua2xamarin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.dll
.java
.class
.jar
.so
/*/obj/
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Any raw assets you want to be deployed with your application can be placed in
this directory (and child directories) and given a Build Action of "AndroidAsset".

These files will be deployed with your package and will be accessible using Android's
AssetManager, like this:

public class ReadAsset : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);

InputStream input = Assets.Open ("my_asset.txt");
}
}

Additionally, some Android functions will automatically load asset files:

Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");

Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
using System;
using System.IO;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using pjsua2xamarin;
using pjsua2xamarin.Droid;
using Android.App;
using Android.Content;
using Android.Hardware;
using Android.Views;
using Android.Graphics;
using Android.Widget;
using Android.Renderscripts;
using static Xamarin.Essentials.Permissions;
using pjsua2xamarin.pjsua2;
using AndroidX.Lifecycle;
using System.Runtime.InteropServices;
using Android.Runtime;

[assembly: ExportRenderer(typeof(CallPage), typeof(CallPageRenderer))]
namespace pjsua2xamarin.Droid
{
public class CallPageRenderer : PageRenderer, ISurfaceHolderCallback
{
global::Android.Widget.Button acceptCallButton;
global::Android.Widget.Button hangupCallButton;
global::Android.Widget.TextView peerTxt;
global::Android.Widget.TextView statusTxt;
global::Android.Views.View view;
private static CallInfo lastCallInfo;
private CallPage callPage;
SurfaceView incomingView;

[DllImport("android")]
private static extern IntPtr ANativeWindow_fromSurface(IntPtr jni, IntPtr surface);

public CallPageRenderer(Context context) : base(context)
{
MessagingCenter.Subscribe<BuddyPage, CallInfo>
(this, "UpdateCallState", (obj, info) => {
if (callPage == null)
return;
lastCallInfo = info as CallInfo;
if (lastCallInfo.state == pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED) {
Device.BeginInvokeOnMainThread(() => {
callPage.Navigation.PopAsync();
});
} else {
Device.BeginInvokeOnMainThread(() => {
updateCallState(lastCallInfo);
});
}
});
MessagingCenter.Subscribe<BuddyPage, CallInfo>
(this, "UpdateMediaCallState", (obj, info) => {
lastCallInfo = info as CallInfo;
if (MyApp.currentCall.vidWin != null) {
incomingView.Visibility = ViewStates.Visible;
}
});
}

~CallPageRenderer() {
MessagingCenter.Unsubscribe<BuddyPage, CallInfo>(this, "UpdateCallState");
MessagingCenter.Unsubscribe<BuddyPage, CallInfo>(this, "UpdateMediaCallState");
}

protected override void OnLayout(bool changed, int l, int t, int r, int b)
{
base.OnLayout(changed, l, t, r, b);

var msw = MeasureSpec.MakeMeasureSpec(r - l, MeasureSpecMode.Exactly);
var msh = MeasureSpec.MakeMeasureSpec(b - t, MeasureSpecMode.Exactly);

view.Measure(msw, msh);
view.Layout(0, 0, r - l, b - t);
}

protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);

if (e.OldElement != null || Element == null) {
return;
}

try {
SetupUserInterface();
SetupEventHandlers();
AddView(view);
callPage = (CallPage)Element;
System.Diagnostics.Debug.WriteLine(@"Call page done initialize");
if (MyApp.currentCall != null) {
try {
lastCallInfo = MyApp.currentCall.getInfo();
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(@"ERROR: ", ex.Message);
}
Device.BeginInvokeOnMainThread(() => {
updateCallState(lastCallInfo);
});
}
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(@"ERROR: ", ex.Message);
}
}

void SetupUserInterface()
{
var activity = this.Context as Activity;
view = activity.LayoutInflater.Inflate(Resource.Layout.activity_call, this, false);

incomingView = view.FindViewById<SurfaceView>(Resource.Id.incomingVideoView);
incomingView.Holder.AddCallback(this);

peerTxt = view.FindViewById<TextView>(Resource.Id.peerTxt);
statusTxt = view.FindViewById<TextView>(Resource.Id.statusTxt);

if (MyApp.currentCall == null || MyApp.currentCall.vidWin == null) {
incomingView.Visibility = ViewStates.Gone;
}
}

void SetupEventHandlers()
{
acceptCallButton = view.FindViewById<global::Android.Widget.Button>(Resource.Id.acceptCallButton);
acceptCallButton.Click += AcceptCallButtonTapped;

hangupCallButton = view.FindViewById<global::Android.Widget.Button>(Resource.Id.hangupCallButton);
hangupCallButton.Click += HangupCallButtonTapped;
}

void AcceptCallButtonTapped(object sender, EventArgs e)
{
CallOpParam prm = new CallOpParam();
prm.statusCode = pjsip_status_code.PJSIP_SC_OK;
try {
MyApp.currentCall.answer(prm);
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(@"ERROR: ", ex.Message);
}

acceptCallButton.Visibility = ViewStates.Gone;
}

void HangupCallButtonTapped(object sender, EventArgs e)
{
if (MyApp.currentCall != null) {
CallOpParam prm = new CallOpParam();
prm.statusCode = pjsip_status_code.PJSIP_SC_DECLINE;
try {
MyApp.currentCall.hangup(prm);
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(@"ERROR: ", ex.Message);
}
}
}

private void updateVideoWindow(bool show)
{
if (MyApp.currentCall != null &&
MyApp.currentCall.vidWin != null &&
MyApp.currentCall.vidPrev != null)
{
long windHandle = 0;
VideoWindowHandle vidWH = new VideoWindowHandle();
if (show) {
IntPtr winPtr = ANativeWindow_fromSurface(JNIEnv.Handle,
incomingView.Holder.Surface.Handle);
windHandle = winPtr.ToInt64();
}
vidWH.handle.setWindow(windHandle);
try {
MyApp.currentCall.vidWin.setWindow(vidWH);
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(@"ERROR: ", ex.Message);
}
}
}

private void updateCallState(CallInfo ci)
{
String call_state = "";

if (ci == null) {
acceptCallButton.Visibility = ViewStates.Gone;
hangupCallButton.Text = "OK";
statusTxt.Text = "Call disconnected";
return;
}

if (ci.role == pjsip_role_e.PJSIP_ROLE_UAC) {
acceptCallButton.Visibility = ViewStates.Gone;
}

if (ci.state <
pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED)
{
if (ci.role == pjsip_role_e.PJSIP_ROLE_UAS) {
call_state = "Incoming call..";
/* Default button texts are already 'Accept' & 'Reject' */
} else {
hangupCallButton.Text = "Cancel";
call_state = ci.stateText;
}
} else if (ci.state >=
pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED)
{
acceptCallButton.Visibility = ViewStates.Gone;
call_state = ci.stateText;
if (ci.state == pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED) {
hangupCallButton.Text = "Hangup";
} else if (ci.state ==
pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED)
{
hangupCallButton.Text = "OK";
call_state = "Call disconnected: " + ci.lastReason;
}
if (ci.state == pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED) {
updateVideoWindow(true);
}
}

peerTxt.Text = ci.remoteUri;
statusTxt.Text = call_state;
}

void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
{
updateVideoWindow(true);
}

void ISurfaceHolderCallback.SurfaceCreated(ISurfaceHolder holder)
{
}

void ISurfaceHolderCallback.SurfaceDestroyed(ISurfaceHolder holder)
{
updateVideoWindow(false);
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;

using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.OS;
using Java.Lang;
using Android.Hardware.Camera2;
using Android.Content;

namespace pjsua2xamarin.Droid
{
[Activity(Label = "pjsua2xamarin", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize )]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);

Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);

IntPtr class_ref = JNIEnv.FindClass("org/pjsip/PjCameraInfo2");
if (class_ref != null) {
IntPtr method_id = JNIEnv.GetStaticMethodID(class_ref,
"SetCameraManager", "(Landroid/hardware/camera2/CameraManager;)V");

if (method_id != null) {
CameraManager manager = GetSystemService(Context.CameraService) as CameraManager;

JNIEnv.CallStaticVoidMethod(class_ref, method_id, new JValue(manager));

Console.WriteLine("SUCCESS setting cameraManager");
}
}
JavaSystem.LoadLibrary("c++_shared");
JavaSystem.LoadLibrary("pjsua2");

LoadApplication(new App());
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.teluu.pjsua2xamarin">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<application android:label="pjsua2xamarin.Android" android:theme="@style/MainTheme"></application>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.CAPTURE_AUDIO_OUTPUT" />
<uses-permission android:name="android.permission.CAPTURE_VIDEO_OUTPUT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
</manifest>
Loading

0 comments on commit 2c56bdc

Please sign in to comment.