1 Commits

Author SHA1 Message Date
QuentinArguillere
6f02c72b46 Add simple project of incoming/outgoing call tests 2021-11-25 15:33:35 +01:00
126 changed files with 1393 additions and 4043 deletions

View File

@@ -27,10 +27,6 @@ Linphone-SDK binaries are fetched from our [Cocoapods repository](https://gitlab
Desktop tutorials are in C#, leveraging on our Nuget packaging.
## Java
Java tutorials targeting the Desktop. If targeting Android, take a look at the Android tutorials instead.
## Additional resources
All tutorials require a SIP account to function, and if you don't have one you can create as many as you want and for free using our [free SIP service](https://subscribe.linphone.org/).

View File

@@ -74,7 +74,7 @@ struct ContentView: View {
HStack {
Text("Login State : ")
.font(.footnote)
Text(tutorialContext.loggedIn ? "Logged in" : "Unregistered")
Text(tutorialContext.loggedIn ? "Looged in" : "Unregistered")
.font(.footnote)
.foregroundColor(tutorialContext.loggedIn ? Color.green : Color.black)
}.padding(.top, 10.0)

View File

@@ -5,7 +5,7 @@ source "https://github.com/CocoaPods/Specs.git"
def basic_pods
if ENV['PODFILE_PATH'].nil?
pod 'linphone-sdk', '~> 5.2.66'
pod 'linphone-sdk', '~> 5.0.48'
else
pod 'linphone-sdk', :path => ENV['PODFILE_PATH'] # local sdk
end

View File

@@ -74,11 +74,6 @@ extension CallKitProviderDelegate: CXProviderDelegate {
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
do {
// The audio stream is going to start shortly: the AVAudioSession must be configured now.
// It is worth to note that an application does not have permission to configure the
// AVAudioSession outside of this delegate action while it is running in background,
// which is usually the case in an incoming call scenario.
tutorialContext.mCore.configureAudioSession();
try tutorialContext.mCall?.accept()
tutorialContext.isCallRunning = true
} catch {
@@ -88,27 +83,17 @@ extension CallKitProviderDelegate: CXProviderDelegate {
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
// This tutorial is not doing outgoing calls. If it had to do so,
// configureAudioSession() shall be called from here, just before launching the
// call.
// tutorialContext.mCore.configureAudioSession();
// tutorialContext.mCore.invite("sip:bob@example.net");
// action.fulfill();
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {}
func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {}
func provider(_ provider: CXProvider, perform action: CXPlayDTMFCallAction) {}
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {}
func providerDidReset(_ provider: CXProvider) {}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
// The linphone Core must be notified that CallKit has activated the AVAudioSession
// in order to start streaming audio.
tutorialContext.mCore.activateAudioSession(actived: true)
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
// The linphone Core must be notified that CallKit has deactivated the AVAudioSession.
tutorialContext.mCore.activateAudioSession(actived: false)
}
}

View File

@@ -0,0 +1,418 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
6644275A274F952D00EF03AA /* AudioRouteInvestigationApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66442759274F952D00EF03AA /* AudioRouteInvestigationApp.swift */; };
6644275C274F952D00EF03AA /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6644275B274F952D00EF03AA /* ContentView.swift */; };
6644275E274F952F00EF03AA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6644275D274F952F00EF03AA /* Assets.xcassets */; };
66442761274F952F00EF03AA /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 66442760274F952F00EF03AA /* Preview Assets.xcassets */; };
EFDB99DC9232C7DED75C1132 /* Pods_AudioRouteInvestigation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 82346E676A805A0558805BF9 /* Pods_AudioRouteInvestigation.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
57D00E4F6DE7380AAF3F0C47 /* Pods-AudioRouteInvestigation.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AudioRouteInvestigation.release.xcconfig"; path = "Target Support Files/Pods-AudioRouteInvestigation/Pods-AudioRouteInvestigation.release.xcconfig"; sourceTree = "<group>"; };
66442756274F952D00EF03AA /* AudioRouteInvestigation.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AudioRouteInvestigation.app; sourceTree = BUILT_PRODUCTS_DIR; };
66442759274F952D00EF03AA /* AudioRouteInvestigationApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRouteInvestigationApp.swift; sourceTree = "<group>"; };
6644275B274F952D00EF03AA /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
6644275D274F952F00EF03AA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
66442760274F952F00EF03AA /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
82346E676A805A0558805BF9 /* Pods_AudioRouteInvestigation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AudioRouteInvestigation.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8DBEF2EA75C5F7144D91A733 /* Pods-AudioRouteInvestigation.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AudioRouteInvestigation.debug.xcconfig"; path = "Target Support Files/Pods-AudioRouteInvestigation/Pods-AudioRouteInvestigation.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
66442753274F952D00EF03AA /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
EFDB99DC9232C7DED75C1132 /* Pods_AudioRouteInvestigation.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
23C37AA5C87C768D9E2DB7FB /* Pods */ = {
isa = PBXGroup;
children = (
8DBEF2EA75C5F7144D91A733 /* Pods-AudioRouteInvestigation.debug.xcconfig */,
57D00E4F6DE7380AAF3F0C47 /* Pods-AudioRouteInvestigation.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
6644274D274F952D00EF03AA = {
isa = PBXGroup;
children = (
66442758274F952D00EF03AA /* AudioRouteInvestigation */,
66442757274F952D00EF03AA /* Products */,
23C37AA5C87C768D9E2DB7FB /* Pods */,
6C18380DFC079682CAFAE959 /* Frameworks */,
);
sourceTree = "<group>";
};
66442757274F952D00EF03AA /* Products */ = {
isa = PBXGroup;
children = (
66442756274F952D00EF03AA /* AudioRouteInvestigation.app */,
);
name = Products;
sourceTree = "<group>";
};
66442758274F952D00EF03AA /* AudioRouteInvestigation */ = {
isa = PBXGroup;
children = (
66442759274F952D00EF03AA /* AudioRouteInvestigationApp.swift */,
6644275B274F952D00EF03AA /* ContentView.swift */,
6644275D274F952F00EF03AA /* Assets.xcassets */,
6644275F274F952F00EF03AA /* Preview Content */,
);
path = AudioRouteInvestigation;
sourceTree = "<group>";
};
6644275F274F952F00EF03AA /* Preview Content */ = {
isa = PBXGroup;
children = (
66442760274F952F00EF03AA /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
6C18380DFC079682CAFAE959 /* Frameworks */ = {
isa = PBXGroup;
children = (
82346E676A805A0558805BF9 /* Pods_AudioRouteInvestigation.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
66442755274F952D00EF03AA /* AudioRouteInvestigation */ = {
isa = PBXNativeTarget;
buildConfigurationList = 66442764274F952F00EF03AA /* Build configuration list for PBXNativeTarget "AudioRouteInvestigation" */;
buildPhases = (
59A6F8BC0224E743CE93CA1E /* [CP] Check Pods Manifest.lock */,
66442752274F952D00EF03AA /* Sources */,
66442753274F952D00EF03AA /* Frameworks */,
66442754274F952D00EF03AA /* Resources */,
6B7520AC43BE168C1B667379 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = AudioRouteInvestigation;
productName = AudioRouteInvestigation;
productReference = 66442756274F952D00EF03AA /* AudioRouteInvestigation.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
6644274E274F952D00EF03AA /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1310;
LastUpgradeCheck = 1310;
TargetAttributes = {
66442755274F952D00EF03AA = {
CreatedOnToolsVersion = 13.1;
};
};
};
buildConfigurationList = 66442751274F952D00EF03AA /* Build configuration list for PBXProject "AudioRouteInvestigation" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 6644274D274F952D00EF03AA;
productRefGroup = 66442757274F952D00EF03AA /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
66442755274F952D00EF03AA /* AudioRouteInvestigation */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
66442754274F952D00EF03AA /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
66442761274F952F00EF03AA /* Preview Assets.xcassets in Resources */,
6644275E274F952F00EF03AA /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
59A6F8BC0224E743CE93CA1E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-AudioRouteInvestigation-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
6B7520AC43BE168C1B667379 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-AudioRouteInvestigation/Pods-AudioRouteInvestigation-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-AudioRouteInvestigation/Pods-AudioRouteInvestigation-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AudioRouteInvestigation/Pods-AudioRouteInvestigation-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
66442752274F952D00EF03AA /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
6644275C274F952D00EF03AA /* ContentView.swift in Sources */,
6644275A274F952D00EF03AA /* AudioRouteInvestigationApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
66442762274F952F00EF03AA /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
66442763274F952F00EF03AA /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
66442765274F952F00EF03AA /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 8DBEF2EA75C5F7144D91A733 /* Pods-AudioRouteInvestigation.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"AudioRouteInvestigation/Preview Content\"";
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Microphone access";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = BC.AudioRouteInvestigation;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
66442766274F952F00EF03AA /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 57D00E4F6DE7380AAF3F0C47 /* Pods-AudioRouteInvestigation.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"AudioRouteInvestigation/Preview Content\"";
DEVELOPMENT_TEAM = Z2V957B3D6;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSMicrophoneUsageDescription = "Microphone access";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = BC.AudioRouteInvestigation;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
66442751274F952D00EF03AA /* Build configuration list for PBXProject "AudioRouteInvestigation" */ = {
isa = XCConfigurationList;
buildConfigurations = (
66442762274F952F00EF03AA /* Debug */,
66442763274F952F00EF03AA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
66442764274F952F00EF03AA /* Build configuration list for PBXNativeTarget "AudioRouteInvestigation" */ = {
isa = XCConfigurationList;
buildConfigurations = (
66442765274F952F00EF03AA /* Debug */,
66442766274F952F00EF03AA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 6644274E274F952D00EF03AA /* Project object */;
}

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,142 @@
//
// AudioRouteInvestigationApp.swift
// AudioRouteInvestigation
//
// Created by QuentinArguillere on 25/11/2021.
//
import SwiftUI
import linphonesw
class AudioRouteInvestigation : ObservableObject
{
var mCore: Core!
var mCoreDelegate : CoreDelegate!
/* PLEASE FILL THESE FIELDS WITH YOUR SETTINGS */
var username : String = "user"
var passwd : String = "password"
var domain : String = "sip.linphone.org"
var remoteAddress : String = "sip:remote@sip.linphone.org"
@Published var loggedIn = false
@Published var callMsg : String = ""
@Published var isCallOutgoing : Bool = false
@Published var isCallIncoming : Bool = false
@Published var isCallRunning : Bool = false
@Published var isCallPaused = false
init() {
LoggingService.Instance.logLevel = LogLevel.Debug
try? mCore = Factory.Instance.createCore(configPath: "", factoryConfigPath: "", systemContext: nil)
//self.mCore.defaultOutputAudioDevice = self.mCore.audioDevices.first { $0.type == AudioDeviceType.Speaker }
mCoreDelegate = CoreDelegateStub( onCallStateChanged: { (core: Core, call: Call, state: Call.State, message: String) in
self.callMsg = message
if (state == .IncomingReceived) {
self.isCallIncoming = true
} else if (state == .OutgoingProgress) {
self.isCallOutgoing = true
} else if (state == .StreamsRunning) {
self.isCallOutgoing = false
self.isCallIncoming = false
self.isCallRunning = true
//self.mCore.outputAudioDevice = self.mCore.audioDevices.first { $0.type == AudioDeviceType.Speaker }
} else if (state == .Released) {
self.isCallOutgoing = false
self.isCallIncoming = false
self.isCallRunning = false
self.isCallPaused = false
}
}, onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in
NSLog("New registration state is \(state) for user id \( String(describing: account.params?.identityAddress?.asString()))\n")
if (state == .Ok) {
self.loggedIn = true
} else if (state == .Cleared) {
self.loggedIn = false
}
})
mCore.addDelegate(delegate: mCoreDelegate)
try? mCore.start()
login()
}
func login() {
do {
let authInfo = try Factory.Instance.createAuthInfo(username: username, userid: "", passwd: passwd, ha1: "", realm: "", domain: domain)
let accountParams = try mCore.createAccountParams()
let identity = try Factory.Instance.createAddress(addr: String("sip:" + username + "@" + domain))
try! accountParams.setIdentityaddress(newValue: identity)
let address = try Factory.Instance.createAddress(addr: String("sip:" + domain))
try address.setTransport(newValue: TransportType.Tls)
try accountParams.setServeraddress(newValue: address)
accountParams.registerEnabled = true
let account = try mCore.createAccount(params: accountParams)
mCore.addAuthInfo(info: authInfo)
try mCore.addAccount(account: account)
mCore.defaultAccount = account
} catch { NSLog(error.localizedDescription) }
}
func outgoingCall() {
do {
let remoteAddress = try Factory.Instance.createAddress(addr: remoteAddress)
let params = try mCore.createCallParams(call: nil)
params.mediaEncryption = MediaEncryption.None
//mCore.defaultOutputAudioDevice = mCore.audioDevices.first { $0.type == AudioDeviceType.Speaker }
let call = mCore.inviteAddressWithParams(addr: remoteAddress, params: params)
//call?.outputAudioDevice = mCore.audioDevices.first { $0.type == AudioDeviceType.Speaker }
} catch { NSLog(error.localizedDescription) }
}
func incomingCall() {
try? mCore.currentCall?.accept()
}
func terminateCall() {
do {
if (mCore.callsNb == 0) { return }
let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0]
if let call = coreCall {
try call.terminate()
}
} catch { NSLog(error.localizedDescription) }
}
func pauseOrResume() {
do {
if (mCore.callsNb == 0) { return }
let coreCall = (mCore.currentCall != nil) ? mCore.currentCall : mCore.calls[0]
if let call = coreCall {
if (call.state != Call.State.Paused && call.state != Call.State.Pausing) {
try call.pause()
isCallPaused = true
} else if (call.state != Call.State.Resuming) {
try call.resume()
isCallPaused = false
}
}
} catch { NSLog(error.localizedDescription) }
}
}
@main
struct AudioRouteInvestigationApp: App {
@ObservedObject var audioRouteInvestigation = AudioRouteInvestigation()
var body: some Scene {
WindowGroup {
ContentView(audioRouteInvestigation: audioRouteInvestigation)
}
}
}

View File

@@ -0,0 +1,82 @@
//
// ContentView.swift
// AudioRouteInvestigation
//
// Created by QuentinArguillere on 25/11/2021.
//
import SwiftUI
import linphonesw
struct ContentView: View {
@ObservedObject var audioRouteInvestigation : AudioRouteInvestigation
func callStateString() -> String {
if (audioRouteInvestigation.isCallOutgoing) {
return "Call Outgoing"
} else if (audioRouteInvestigation.isCallIncoming) {
return "Call Incoming"
} else if (audioRouteInvestigation.isCallRunning) {
return "Call running"
} else {
return "No Call"
}
}
var body: some View {
VStack {
VStack {
HStack {
Text(audioRouteInvestigation.username)
Text(audioRouteInvestigation.loggedIn ? "REGISTERED" : "UNREGISTERED").foregroundColor(audioRouteInvestigation.loggedIn ? Color.green : Color.red)
}
Button(action: {
if (self.audioRouteInvestigation.isCallIncoming) {
self.audioRouteInvestigation.incomingCall()
} else {
self.audioRouteInvestigation.outgoingCall()
}
}){
Text( self.audioRouteInvestigation.isCallIncoming ? "Accept Incoming Call" : "Start Outgoing Call")
.font(.largeTitle)
.foregroundColor(Color.white)
.frame(width: 340.0, height: 45.0)
.background(Color.gray)
}.disabled(audioRouteInvestigation.isCallRunning)
Button(action: audioRouteInvestigation.pauseOrResume) {
Text(audioRouteInvestigation.isCallPaused ? "Resume call" : "Pause call")
.font(.largeTitle)
.foregroundColor(Color.white)
.frame(width: 340.0, height: 42.0)
.background(Color.gray)
}.padding(.top, 50).disabled(!audioRouteInvestigation.isCallRunning)
Button(action: audioRouteInvestigation.terminateCall) {
Text( "Terminate call")
.font(.largeTitle)
.foregroundColor(Color.white)
.frame(width: 340.0, height: 42.0)
.background(Color.gray)
}.padding(.top, 50).disabled(!audioRouteInvestigation.isCallRunning)
HStack {
Text("Call state: ").font(.title3).underline()
Text(callStateString())
Spacer()
}
HStack {
Text("Current Call msg: ").font(.title3).underline()
Text(audioRouteInvestigation.callMsg)
Spacer()
}.padding(.top, 50)
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(audioRouteInvestigation: AudioRouteInvestigation())
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,24 @@
# Uncomment the next line to define a global platform for your project
platform :ios, '11.0'
source "https://gitlab.linphone.org/BC/public/podspec.git"
source "https://github.com/CocoaPods/Specs.git"
def basic_pods
if ENV['PODFILE_PATH'].nil?
pod 'linphone-sdk', '~> 5.0.53'
else
pod 'linphone-sdk', :path => ENV['PODFILE_PATH'] # local sdk
end
end
target 'AudioRouteInvestigation' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
# Pods for AudioRouteInvestigation
basic_pods
end

6
java/.gitignore vendored
View File

@@ -1,6 +0,0 @@
.gradle
build
linphone-sdk*.jar
linphone-sdk*.zip
.*.db
.*.sqlite3

View File

@@ -1,8 +0,0 @@
Hello World tutorial
====================
The purpose of this tutorial is to explain how to add our SDK as a dependency of an Java project and how to create the `Core` object that all our APIs depends on.
Start by taking a look at the `build.gradle` file to see how we reference the Linphone SDK and add it as a dependency of our project.
The user interface will only display the `Core`'s version, but in the next tutorial you will learn how to use it to login your SIP account.

View File

@@ -1,18 +0,0 @@
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.1.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation files('linphone-sdk.jar')
}
javafx {
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
mainClassName = 'LinphoneJavaFX'

View File

@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,63 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.linphone.core.Core;
import org.linphone.core.Factory;
import org.linphone.core.LogLevel;
import org.linphone.core.LoggingService;
public class LinphoneJavaFX extends Application {
@Override
public void start(Stage stage) throws Exception {
// Core is the main object of the SDK. You can't do much without it
// To create a Core, we need the instance of the Factory.
Factory factory = Factory.instance();
factory.setDataDir(".");
// Some configuration can be done before the Core is created, for example enable logs.
LoggingService loggingService = factory.getLoggingService();
loggingService.setLogLevel(LogLevel.Message);
// Your Core can use up to 2 configuration files, but that isn't mandatory.
// The third parameter is the application context, which is *not* mandatory when working with Java.
// You can now create your Core object.
Core core = factory.createCore("", "", null);
// Once you have your core you can start to do a lot of things, get its version for example.
String version = core.getVersion();
Label l = new Label("Hello world, Linphone core version is " + version);
Scene scene = new Scene(new StackPane(l), 640, 480);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch();
}
}

View File

@@ -1,10 +0,0 @@
Account login tutorial
====================
Now that you have set up Linphone-SDK in a Java project, let's start using it.
We will see how to login on a SIP server using the `Core` object instanciated in the previous tutorial.
If you don't have a SIP server yet, you can create an account for free using our [free SIP service](https://subscribe.linphone.org/).
Once you'll be logged-in, you'll be able to continue to the next tutorials to make calls.

View File

@@ -1,18 +0,0 @@
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.1.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation files('linphone-sdk.jar')
}
javafx {
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
mainClassName = 'LinphoneJavaFX'

View File

@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,47 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import service.CoreService;
import javafx.fxml.FXMLLoader;
public class LinphoneJavaFX extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/view/LoginPage.fxml"));
stage.setTitle("Linphone SDK example");
stage.setScene(new Scene(root));
stage.show();
}
@Override
public void stop() {
CoreService.instance().stop();
System.exit(0);
}
public static void main(String[] args) {
launch();
}
}

View File

@@ -1,154 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import service.CoreService;
import org.linphone.core.Account;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.RegistrationState;
import org.linphone.core.TransportType;
public class LoginController extends CoreListenerStub implements Initializable {
@FXML
private Button loginButton;
@FXML
private Button logoutButton;
@FXML
private TextField identityField;
@FXML
private PasswordField passwordField;
@FXML
private RadioButton tlsRadio;
@FXML
private RadioButton tcpRadio;
@FXML
private RadioButton udpRadio;
@FXML
private Label loginLabel;
@FXML
private Label registrationLabel;
private CoreService coreService = CoreService.instance();
@FXML
public void initialize(URL location, ResourceBundle resourceBundle) {
// The Core is the main object of the SDK. You can't do much without it.
// If you're not familiar with Linphone Core creation, see the 0-HelloWorld project.
Core core = CoreService.instance().core;
// In this tutorial we are going to log in and our registration state will change.
// To get callbacks from the Core and be notified of the registration state change,
// we need to register ourself as a listener of the core.
core.addListener(this);
// Start the core after setup, and before everything else.
coreService.start();
// Setup GUI.
logoutGuiChanges();
// Now use the GUI to log in, and see LogInClick to see how to handle login.
}
// Called when you click on the "Login" button.
@FXML
private void onLoginClicked() {
if (loginButton.isDisabled())
return;
loginButton.setDisable(true);
TransportType transport = TransportType.Udp;
if (tlsRadio.isSelected()) {
transport = TransportType.Tls;
} else if (tcpRadio.isSelected()) {
transport = TransportType.Tcp;
}
coreService.login(identityField.getText(), passwordField.getText(), transport);
}
// Called when you click on the "Logout" button.
@FXML
private void onLogoutClicked() {
if (logoutButton.isDisabled())
return;
logoutButton.setDisable(true);
coreService.logout();
}
private void logoutGuiChanges() {
loginButton.setDisable(false);
logoutButton.setDisable(true);
loginLabel.setText("You are logged out");
}
private void loginGuiChanges() {
loginButton.setDisable(true);
logoutButton.setDisable(false);
loginLabel.setText("You are logged in, with identity " + CoreService.instance().core.getIdentity() + ".");
}
private void loginInProgressGuiChanges() {
loginButton.setDisable(true);
logoutButton.setDisable(true);
loginLabel.setText("Login in progress, with identity " + CoreService.instance().core.getIdentity() + ".");
}
private void loginFailedGuiChanges() {
loginButton.setDisable(false);
logoutButton.setDisable(true);
loginLabel.setText("Login failed, try again.");
}
// This method is called every time the RegistrationState is updated by background core's actions.
// In this example we use this to update the GUI.
@Override
public void onAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, String message) {
registrationLabel.setText("Your registration state is: " + state.toString());
switch (state) {
case Cleared:
case None:
coreService.clearCoreAfterLogout();
logoutGuiChanges();
break;
case Ok:
loginGuiChanges();
break;
case Progress:
loginInProgressGuiChanges();
break;
case Failed:
loginFailedGuiChanges();
break;
default:
break;
}
}
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package service;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Timer;
import java.util.TimerTask;
import org.linphone.core.Account;
import org.linphone.core.AccountParams;
import org.linphone.core.Address;
import org.linphone.core.AuthInfo;
import org.linphone.core.Call;
import org.linphone.core.CallParams;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory;
import org.linphone.core.GlobalState;
import org.linphone.core.LogLevel;
import org.linphone.core.LoggingService;
import org.linphone.core.TransportType;
import org.linphone.core.VideoActivationPolicy;
import javafx.application.Platform;
public class CoreService extends CoreListenerStub {
private static CoreService instance;
public Core core;
public IterateRunnable iterateRunnable;
private Timer iterateTimer;
private CoreService() {
Factory factory = Factory.instance();
factory.setDataDir(".");
LoggingService loggingService = factory.getLoggingService();
loggingService.setLogLevel(LogLevel.Message);
core = factory.createCore("", "", null);
core.setAudioPort(7666);
core.setUserCertificatesPath(Paths.get("").toString());
}
public static CoreService instance() {
if (instance == null) {
instance = new CoreService();
}
return instance;
}
public void start() {
this.core.start();
// The iterate method of the Core is to be called regularly, typically at 20 ms interval.
// So we create an IterateRunnable and a Timer to execute this call every 20 ms.
iterateRunnable = new IterateRunnable();
iterateTimer = new Timer();
iterateTimer.schedule(new IterateTimerTask(), 20, 20);
}
public void stop() {
core.stop();
}
public void login(String identity, String password, TransportType transport) {
// To configure a SIP account, we need an Account object and an AuthInfo object.
// The first one is how to connect to the proxy server, the second one stores the credentials.
// Here we are creating an AuthInfo object from the identity Address and password provided by the user.
Address address = Factory.instance().createAddress(identity);
// The AuthInfo can be created from the Factory as it's only a data class.
// userID is set to "" as it's the same as the username in our case.
// ha1 is set to "" as we are using the clear text password. Upon first register, the hash will be computed automatically.
// The realm will be determined automatically from the first register, as well as the algorithm.
AuthInfo authInfo = Factory.instance().createAuthInfo(address.getUsername(), "", password, "", "",
address.getDomain());
// And we add it to the Core.
core.addAuthInfo(authInfo);
// Then we create an AccountParams object.
// It contains the account informations needed by the core.
AccountParams accountParams = core.createAccountParams();
// A SIP account is identified by an identity address that we can construct from the username and domain.
accountParams.setIdentityAddress(address);
// We also need to configure where the proxy server is located.
Address serverAddress = Factory.instance().createAddress("sip:" + address.getDomain());
// We use the Address object to easily set the transport protocol.
serverAddress.setTransport(transport);
accountParams.setServerAddress(serverAddress);
// If setRegisterEnabled(true), when this account will be added to the core it will automatically try to connect.
accountParams.setRegisterEnabled(true);
// We can now create an Account object from the AccountParams...
Account account = core.createAccount(accountParams);
// ... and add it to the core, launching the connection process.
core.addAccount(account);
// Also set the newly added account as default.
core.setDefaultAccount(account);
}
public void logout() {
// setRegisterEnabled(false) on a connected Account object will launch the logout action.
Account account = core.getDefaultAccount();
if (account != null) {
// BUT BE CAREFUL : the Params attribute of an account is read-only, you MUST Clone it.
AccountParams accountParams = account.getParams().clone();
// Then you can modify the clone.
accountParams.setRegisterEnabled(false);
// And finally setting the new Params value triggers the changes, here the logout.
account.setParams(accountParams);
}
}
public void clearCoreAfterLogout() {
core.clearAllAuthInfo();
core.clearAccounts();
}
@Override
public void onGlobalStateChanged(Core core, GlobalState state, String message) {
if (state == GlobalState.Off) {
iterateTimer.cancel();
}
}
}
class IterateTimerTask extends TimerTask {
@Override
public void run() {
Platform.runLater(CoreService.instance().iterateRunnable);
}
}
class IterateRunnable implements Runnable {
@Override
public void run() {
CoreService.instance().core.iterate();
}
}

View File

@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<Pane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.LoginController">
<children>
<VBox prefHeight="400.0" prefWidth="600.0">
<children>
<GridPane vgap="20.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="350.0" minWidth="350.0" prefWidth="350.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="Identity:" GridPane.halignment="CENTER" />
<TextField id="Identity" fx:id="identityField" maxWidth="350.0" minWidth="350.0" prefWidth="350.0" promptText="sip:" GridPane.columnIndex="1" GridPane.halignment="CENTER" />
<Label text="Password:" GridPane.halignment="CENTER" GridPane.rowIndex="1" />
<PasswordField fx:id="passwordField" promptText="password" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<HBox alignment="CENTER" prefHeight="60.0" prefWidth="200.0" spacing="50.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
<children>
<RadioButton id="TlsRadio" fx:id="tlsRadio" mnemonicParsing="false" selected="true" text="TLS">
<toggleGroup>
<ToggleGroup fx:id="TransportGroup" />
</toggleGroup>
</RadioButton>
<RadioButton id="TcpRadio" fx:id="tcpRadio" mnemonicParsing="false" text="TCP" toggleGroup="$TransportGroup" />
<RadioButton id="UdpRadio" fx:id="udpRadio" mnemonicParsing="false" text="UDP" toggleGroup="$TransportGroup" />
</children>
</HBox>
<Label text="Transport:" GridPane.halignment="CENTER" GridPane.rowIndex="2" />
</children>
</GridPane>
<GridPane VBox.vgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label id="LoginText" fx:id="loginLabel" alignment="TOP_LEFT" GridPane.halignment="CENTER" />
<Label fx:id="registrationLabel" alignment="TOP_LEFT" GridPane.halignment="CENTER" GridPane.rowIndex="1" />
</children>
</GridPane>
<Separator prefWidth="200.0" />
<ButtonBar prefHeight="40.0" prefWidth="200.0">
<buttons>
<Button fx:id="loginButton" defaultButton="true" mnemonicParsing="false" onMouseClicked="#onLoginClicked" text="Login" />
<Button fx:id="logoutButton" cancelButton="true" disable="true" mnemonicParsing="false" onMouseClicked="#onLogoutClicked" text="Logout" />
</buttons>
</ButtonBar>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>
</children>
</Pane>

View File

@@ -1,8 +0,0 @@
Incoming call tutorial
====================
This tutorial will focus on how the app will be notified when a call is being received and how to either accept it or terminate it.
We'll also cover how to toggle the microphone and the speakerphone during an active call.
If you want to test it on either a device, you'll need another SIP client to make the call. If you don't, you can use the [outgoing call tutorial](https://gitlab.linphone.org/BC/public/tutorials/-/tree/master/java/4-OutgoingCall) to do it.

View File

@@ -1,18 +0,0 @@
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.1.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation files('linphone-sdk.jar')
}
javafx {
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
mainClassName = 'LinphoneJavaFX'

View File

@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
import controller.ScreenController;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import service.CoreService;
import javafx.fxml.FXMLLoader;
public class LinphoneJavaFX extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/view/LoginPage.fxml"));
stage.setTitle("Linphone SDK example");
stage.setScene(new Scene(root));
ScreenController screenController = new ScreenController(stage.getScene());
screenController.add("login", "/view/LoginPage.fxml");
screenController.add("call", "/view/CallPage.fxml");
screenController.activate("login");
CoreService.instance().screenController = screenController;
stage.show();
}
@Override
public void stop() {
CoreService.instance().stop();
System.exit(0);
}
public static void main(String[] args) {
launch();
}
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
import org.linphone.core.Call;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Reason;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import service.CoreService;
public class CallController extends CoreListenerStub implements ScreenInterface {
private CoreService coreService = CoreService.instance();
private Call incomingCall;
@FXML
private TitledPane callPane;
@FXML
private Button hangupButton;
@FXML
private Button muteSoundButton;
@FXML
private Button muteMicrophoneButton;
@FXML
private VBox incomingCallVBox;
@FXML
private Label incomingCallLabel;
@FXML
private Button answerButton;
@FXML
private Button declineButton;
@FXML
private Label callLabel;
@FXML
private void onHangUpClicked() {
coreService.core.terminateAllCalls();
}
@FXML
private void onMuteSoundClicked() {
if (coreService.toggleSpeaker()) {
muteSoundButton.setText("Activate sound");
} else {
muteSoundButton.setText("Mute sound");
}
}
@FXML
private void onMuteMicrophoneClicked() {
if (coreService.toggleMicrophone()) {
muteMicrophoneButton.setText("Activate microphone");
} else {
muteMicrophoneButton.setText("Mute microphone");
}
}
@FXML
private void onAnswerClicked() {
if (incomingCall == null)
return;
incomingCall.accept();
incomingCall = null;
}
@FXML
private void onDeclineClicked() {
if (incomingCall == null)
return;
incomingCall.decline(Reason.Declined);
incomingCall = null;
}
private void callInProgressGuiUpdates() {
incomingCallVBox.setVisible(false);
hangupButton.setDisable(false);
muteSoundButton.setDisable(false);
muteMicrophoneButton.setDisable(false);
}
private void endingCallGuiUpdates() {
incomingCallVBox.setVisible(false);
hangupButton.setDisable(true);
muteSoundButton.setDisable(true);
muteSoundButton.setText("Mute sound");
muteMicrophoneButton.setDisable(true);
muteMicrophoneButton.setText("Mute microphone");
}
public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
callLabel.setText("Your call state is: " + state.toString());
switch (state) {
case IncomingReceived:
incomingCall = call;
incomingCallVBox.setVisible(true);
incomingCallLabel.setText(incomingCall.getRemoteAddress().asString());
break;
case StreamsRunning:
callInProgressGuiUpdates();
break;
case Error:
case End:
case Released:
incomingCall = null;
endingCallGuiUpdates();
break;
default:
break;
}
}
@Override
public void onNavigatedFrom() {
coreService.core.removeListener(this);
}
@Override
public void onNavigatedTo() {
callPane.setText("Hello " + coreService.core.getDefaultProxyConfig().findAuthInfo().getUsername());
coreService.core.addListener(this);
if (coreService.core.getCurrentCall() != null) {
onCallStateChanged(coreService.core, coreService.core.getCurrentCall(),
coreService.core.getCurrentCall().getState(), null);
}
}
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import service.CoreService;
import org.linphone.core.Account;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.RegistrationState;
public class LoginController extends CoreListenerStub implements Initializable, ScreenInterface {
@FXML
private Button loginButton;
@FXML
private TextField identityField;
@FXML
private PasswordField passwordField;
@FXML
private Label registrationLabel;
private CoreService coreService = CoreService.instance();
@FXML
public void initialize(URL location, ResourceBundle resourceBundle) {
coreService.start();
}
@FXML
private void onLoginClicked() {
if (loginButton.isDisabled())
return;
loginButton.setDisable(true);
coreService.login(identityField.getText(), passwordField.getText());
}
@Override
public void onAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, String message) {
registrationLabel.setText("Your registration state is: " + state.toString());
switch (state) {
case Cleared:
case None:
coreService.clearCoreAfterLogout();
loginButton.setDisable(false);
break;
case Ok:
loginButton.setDisable(true);
coreService.screenController.activate("call");
break;
case Progress:
loginButton.setDisable(true);
break;
case Failed:
loginButton.setDisable(false);
break;
default:
break;
}
}
@Override
public void onNavigatedFrom() {
coreService.core.removeListener(this);
}
@Override
public void onNavigatedTo() {
coreService.core.addListener(this);
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
import java.io.IOException;
import java.util.HashMap;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
class Screen {
public Parent root;
public FXMLLoader loader;
Screen(Parent root, FXMLLoader loader) {
this.root = root;
this.loader = loader;
}
}
public class ScreenController {
private HashMap<String, Screen> screenMap = new HashMap<>();
private Scene main;
private Screen currentScreen;
public ScreenController(Scene main) {
this.main = main;
}
public void add(String name, String viewFile) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource(viewFile));
Parent root = loader.load();
screenMap.put(name, new Screen(root, loader));
}
public void remove(String name) {
screenMap.remove(name);
}
public void activate(String name) {
if (currentScreen != null) {
currentScreen.loader.<ScreenInterface>getController().onNavigatedFrom();
}
currentScreen = screenMap.get(name);
main.setRoot(currentScreen.root);
currentScreen.loader.<ScreenInterface>getController().onNavigatedTo();
}
}

View File

@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
public interface ScreenInterface {
public void onNavigatedFrom();
public void onNavigatedTo();
}

View File

@@ -1,148 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package service;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Timer;
import java.util.TimerTask;
import org.linphone.core.Account;
import org.linphone.core.AccountParams;
import org.linphone.core.Address;
import org.linphone.core.AuthInfo;
import org.linphone.core.Call;
import org.linphone.core.CallParams;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory;
import org.linphone.core.GlobalState;
import org.linphone.core.LogLevel;
import org.linphone.core.LoggingService;
import org.linphone.core.VideoActivationPolicy;
import controller.ScreenController;
import javafx.application.Platform;
public class CoreService extends CoreListenerStub {
private static CoreService instance;
public Core core;
public ScreenController screenController;
public IterateRunnable iterateRunnable;
private Timer iterateTimer;
private CoreService() {
Factory factory = Factory.instance();
factory.setDataDir(".");
LoggingService loggingService = factory.getLoggingService();
loggingService.setLogLevel(LogLevel.Message);
core = factory.createCore("", "", null);
core.setAudioPort(7666);
core.setUserCertificatesPath(Paths.get("").toString());
}
public static CoreService instance() {
if (instance == null) {
instance = new CoreService();
}
return instance;
}
public void start() {
this.core.start();
iterateRunnable = new IterateRunnable();
iterateTimer = new Timer();
iterateTimer.schedule(new IterateTimerTask(), 20, 20);
}
public void stop() {
core.stop();
}
public void login(String identity, String password) {
Address address = Factory.instance().createAddress(identity);
AuthInfo authInfo = Factory.instance().createAuthInfo(address.getUsername(), "", password, "", "",
address.getDomain());
core.addAuthInfo(authInfo);
AccountParams accountParams = core.createAccountParams();
accountParams.setIdentityAddress(address);
Address serverAddress = Factory.instance().createAddress("sip:" + address.getDomain() + ";transport=tls");
accountParams.setServerAddress(serverAddress);
accountParams.setRegisterEnabled(true);
Account account = core.createAccount(accountParams);
core.addAccount(account);
core.setDefaultAccount(account);
}
public void logout() {
Account account = core.getDefaultAccount();
if (account != null) {
AccountParams accountParams = account.getParams().clone();
accountParams.setRegisterEnabled(false);
account.setParams(accountParams);
}
}
/// Mute/Unmute your microphone.
/// Setting to false on the Core mutes your microphone globally.
public Boolean toggleMicrophone() {
core.setMicEnabled(!core.isMicEnabled());
return core.isMicEnabled();
}
/// Enable/Disable the speaker sound.
/// setSpeakerMuted(true) on a Call object disables the sound output of this call.
public Boolean toggleSpeaker() {
core.getCurrentCall().setSpeakerMuted(!core.getCurrentCall().getSpeakerMuted());
return core.getCurrentCall().getSpeakerMuted();
}
public void clearCoreAfterLogout() {
core.clearAllAuthInfo();
core.clearAccounts();
}
@Override
public void onGlobalStateChanged(Core core, GlobalState state, String message) {
if (state == GlobalState.Off) {
iterateTimer.cancel();
}
}
}
class IterateTimerTask extends TimerTask {
@Override
public void run() {
Platform.runLater(CoreService.instance().iterateRunnable);
}
}
class IterateRunnable implements Runnable {
@Override
public void run() {
CoreService.instance().core.iterate();
}
}

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<TitledPane fx:id="callPane" alignment="TOP_CENTER" animated="false" collapsible="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" text="Hello" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.CallController">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0">
<children>
<Label fx:id="callLabel" alignment="CENTER" text="Your call state is: Idle" />
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="35.0">
<children>
<Button fx:id="hangupButton" disable="true" mnemonicParsing="false" onMouseClicked="#onHangUpClicked" text="Hang up" />
<Button fx:id="muteSoundButton" disable="true" mnemonicParsing="false" onMouseClicked="#onMuteSoundClicked" text="Mute sound" />
<Button fx:id="muteMicrophoneButton" disable="true" mnemonicParsing="false" onMouseClicked="#onMuteMicrophoneClicked" text="Mute microphone" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets top="20.0" />
</VBox.margin>
</VBox>
<VBox fx:id="incomingCallVBox" alignment="CENTER" prefHeight="200.0" prefWidth="100.0" visible="false">
<children>
<Label text="You have a call from:" />
<Label fx:id="incomingCallLabel" />
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="35.0">
<children>
<Button fx:id="answerButton" mnemonicParsing="false" onMouseClicked="#onAnswerClicked" text="Answer" />
<Button fx:id="declineButton" mnemonicParsing="false" onMouseClicked="#onDeclineClicked" text="Decline" />
</children>
</HBox>
</children>
</VBox>
</children>
</VBox>
</children></AnchorPane>
</content>
</TitledPane>

View File

@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<TitledPane alignment="TOP_CENTER" animated="false" collapsible="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" text="Login Form" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/17" fx:controller="controller.LoginController">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<FlowPane alignment="TOP_CENTER" columnHalignment="CENTER" layoutX="199.0" layoutY="54.0" orientation="VERTICAL" prefHeight="200.0" prefWidth="200.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="350.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="Identity:" />
<TextField fx:id="identityField" maxWidth="350.0" minWidth="350.0" prefWidth="350.0" GridPane.columnIndex="1" />
<Label text="Password:" GridPane.rowIndex="1" />
<PasswordField fx:id="passwordField" maxWidth="350.0" minWidth="350.0" prefWidth="350.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
<FlowPane.margin>
<Insets bottom="20.0" top="20.0" />
</FlowPane.margin>
</GridPane>
<Label fx:id="registrationLabel" prefWidth="0.0">
<FlowPane.margin>
<Insets />
</FlowPane.margin>
</Label>
<Separator prefWidth="200.0" />
<ButtonBar prefHeight="40.0" prefWidth="200.0">
<buttons>
<Button fx:id="loginButton" mnemonicParsing="false" onMouseClicked="#onLoginClicked" text="Login" />
</buttons>
</ButtonBar>
</children>
</FlowPane>
</children></AnchorPane>
</content>
</TitledPane>

View File

@@ -1,4 +0,0 @@
Outgoing call tutorial
====================
In the previous tutorial we saw how to handle an incoming call, now let's start one.

View File

@@ -1,18 +0,0 @@
plugins {
id 'application'
id 'org.openjfx.javafxplugin' version '0.1.0'
}
repositories {
mavenCentral()
}
dependencies {
implementation files('linphone-sdk.jar')
}
javafx {
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
mainClassName = 'LinphoneJavaFX'

View File

@@ -1,7 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -1,249 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -1,92 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
import controller.ScreenController;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import service.CoreService;
import javafx.fxml.FXMLLoader;
public class LinphoneJavaFX extends Application {
@Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("/view/LoginPage.fxml"));
stage.setTitle("Linphone SDK example");
stage.setScene(new Scene(root));
ScreenController screenController = new ScreenController(stage.getScene());
screenController.add("login", "/view/LoginPage.fxml");
screenController.add("call", "/view/CallPage.fxml");
screenController.activate("login");
CoreService.instance().screenController = screenController;
stage.show();
}
@Override
public void stop() {
CoreService.instance().stop();
System.exit(0);
}
public static void main(String[] args) {
launch();
}
}

View File

@@ -1,206 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
import org.linphone.core.Call;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Reason;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import service.CoreService;
public class CallController extends CoreListenerStub implements ScreenInterface {
private CoreService coreService = CoreService.instance();
private Call incomingCall;
@FXML
private TitledPane callPane;
@FXML
TextField uriToCallField;
@FXML
private Button callButton;
@FXML
private Button hangupButton;
@FXML
private Button cameraButton;
@FXML
private Button muteSoundButton;
@FXML
private Button muteMicrophoneButton;
@FXML
private VBox incomingCallVBox;
@FXML
private Label incomingCallLabel;
@FXML
private Button answerButton;
@FXML
private Button declineButton;
@FXML
private Label callLabel;
@FXML
private void onCallClicked() {
coreService.call(uriToCallField.getText());
}
@FXML
private void onHangUpClicked() {
coreService.core.terminateAllCalls();
}
@FXML
private void onCameraClicked() {
coreService.toggleCamera();
cameraButton.setText("Waiting...");
cameraButton.setDisable(true);
}
@FXML
private void onMuteSoundClicked() {
if (coreService.toggleSpeaker()) {
muteSoundButton.setText("Activate sound");
} else {
muteSoundButton.setText("Mute sound");
}
}
@FXML
private void onMuteMicrophoneClicked() {
if (coreService.toggleMicrophone()) {
muteMicrophoneButton.setText("Activate microphone");
} else {
muteMicrophoneButton.setText("Mute microphone");
}
}
@FXML
private void onAnswerClicked() {
if (incomingCall == null)
return;
incomingCall.accept();
incomingCall = null;
}
@FXML
private void onDeclineClicked() {
if (incomingCall == null)
return;
incomingCall.decline(Reason.Declined);
incomingCall = null;
}
private void callInProgressGuiUpdates() {
incomingCallVBox.setVisible(false);
callButton.setDisable(true);
hangupButton.setDisable(false);
cameraButton.setDisable(false);
muteSoundButton.setDisable(false);
muteMicrophoneButton.setDisable(false);
}
private void endingCallGuiUpdates() {
incomingCallVBox.setVisible(false);
callButton.setDisable(false);
hangupButton.setDisable(true);
cameraButton.setDisable(true);
cameraButton.setText("Activate camera");
muteSoundButton.setDisable(true);
muteSoundButton.setText("Mute sound");
muteMicrophoneButton.setDisable(true);
muteMicrophoneButton.setText("Mute microphone");
}
private void startVideoAndUpdateGui() {
cameraButton.setText("Deactivate camera");
callButton.setDisable(false);
}
private void stopVideoAndUpdateGui() {
cameraButton.setText("Activate camera");
cameraButton.setDisable(false);
}
public void onCallStateChanged(Core core, Call call, Call.State state, String message) {
callLabel.setText("Your call state is: " + state.toString());
switch (state) {
case IncomingReceived:
incomingCall = call;
incomingCallVBox.setVisible(true);
incomingCallLabel.setText(incomingCall.getRemoteAddress().asString());
break;
case OutgoingInit:
case OutgoingProgress:
case OutgoingRinging:
hangupButton.setDisable(false);
break;
case StreamsRunning:
case UpdatedByRemote:
callInProgressGuiUpdates();
if (call.getCurrentParams().isVideoEnabled()) {
startVideoAndUpdateGui();
} else {
stopVideoAndUpdateGui();
}
break;
case Error:
case End:
case Released:
incomingCall = null;
endingCallGuiUpdates();
break;
default:
break;
}
}
@Override
public void onNavigatedFrom() {
coreService.core.removeListener(this);
}
@Override
public void onNavigatedTo() {
callPane.setText("Hello " + coreService.core.getDefaultProxyConfig().findAuthInfo().getUsername());
coreService.core.addListener(this);
if (coreService.core.getCurrentCall() != null) {
onCallStateChanged(coreService.core, coreService.core.getCurrentCall(),
coreService.core.getCurrentCall().getState(), null);
}
}
}

View File

@@ -1,96 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import service.CoreService;
import org.linphone.core.Account;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.RegistrationState;
public class LoginController extends CoreListenerStub implements Initializable, ScreenInterface {
@FXML
private Button loginButton;
@FXML
private TextField identityField;
@FXML
private PasswordField passwordField;
@FXML
private Label registrationLabel;
private CoreService coreService = CoreService.instance();
@FXML
public void initialize(URL location, ResourceBundle resourceBundle) {
coreService.start();
}
@FXML
private void onLoginClicked() {
if (loginButton.isDisabled())
return;
loginButton.setDisable(true);
coreService.login(identityField.getText(), passwordField.getText());
}
@Override
public void onAccountRegistrationStateChanged(Core core, Account account, RegistrationState state, String message) {
registrationLabel.setText("Your registration state is: " + state.toString());
switch (state) {
case Cleared:
case None:
coreService.clearCoreAfterLogout();
loginButton.setDisable(false);
break;
case Ok:
loginButton.setDisable(true);
coreService.screenController.activate("call");
break;
case Progress:
loginButton.setDisable(true);
break;
case Failed:
loginButton.setDisable(false);
break;
default:
break;
}
}
@Override
public void onNavigatedFrom() {
coreService.core.removeListener(this);
}
@Override
public void onNavigatedTo() {
coreService.core.addListener(this);
}
}

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
import java.io.IOException;
import java.util.HashMap;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
class Screen {
public Parent root;
public FXMLLoader loader;
Screen(Parent root, FXMLLoader loader) {
this.root = root;
this.loader = loader;
}
}
public class ScreenController {
private HashMap<String, Screen> screenMap = new HashMap<>();
private Scene main;
private Screen currentScreen;
public ScreenController(Scene main) {
this.main = main;
}
public void add(String name, String viewFile) throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource(viewFile));
Parent root = loader.load();
screenMap.put(name, new Screen(root, loader));
}
public void remove(String name) {
screenMap.remove(name);
}
public void activate(String name) {
if (currentScreen != null) {
currentScreen.loader.<ScreenInterface>getController().onNavigatedFrom();
}
currentScreen = screenMap.get(name);
main.setRoot(currentScreen.root);
currentScreen.loader.<ScreenInterface>getController().onNavigatedTo();
}
}

View File

@@ -1,25 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package controller;
public interface ScreenInterface {
public void onNavigatedFrom();
public void onNavigatedTo();
}

View File

@@ -1,172 +0,0 @@
/*
* Copyright (c) 2010-2024 Belledonne Communications SARL.
*
* This file is part of Linphone Java Tutorial.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package service;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Timer;
import java.util.TimerTask;
import org.linphone.core.Account;
import org.linphone.core.AccountParams;
import org.linphone.core.Address;
import org.linphone.core.AuthInfo;
import org.linphone.core.Call;
import org.linphone.core.CallParams;
import org.linphone.core.Core;
import org.linphone.core.CoreListenerStub;
import org.linphone.core.Factory;
import org.linphone.core.GlobalState;
import org.linphone.core.LogLevel;
import org.linphone.core.LoggingService;
import org.linphone.core.VideoActivationPolicy;
import controller.ScreenController;
import javafx.application.Platform;
public class CoreService extends CoreListenerStub {
private static CoreService instance;
public Core core;
public ScreenController screenController;
public IterateRunnable iterateRunnable;
private Timer iterateTimer;
private CoreService() {
Factory factory = Factory.instance();
factory.setDataDir(".");
LoggingService loggingService = factory.getLoggingService();
loggingService.setLogLevel(LogLevel.Message);
core = factory.createCore("", "", null);
core.setAudioPort(7666);
core.setVideoPort(9666);
core.setUserCertificatesPath(Paths.get("").toString());
VideoActivationPolicy videoActivationPolicy = factory.createVideoActivationPolicy();
videoActivationPolicy.setAutomaticallyAccept(true);
videoActivationPolicy.setAutomaticallyInitiate(false);
core.setVideoActivationPolicy(videoActivationPolicy);
core.setVideoCaptureEnabled(true); // TODO: core.VideoSupported()
core.usePreviewWindow(true);
core.setVideoDisplayFilter("MSGLXVideo");
}
public static CoreService instance() {
if (instance == null) {
instance = new CoreService();
}
return instance;
}
public void start() {
this.core.start();
iterateRunnable = new IterateRunnable();
iterateTimer = new Timer();
iterateTimer.schedule(new IterateTimerTask(), 20, 20);
}
public void stop() {
core.stop();
}
public void login(String identity, String password) {
Address address = Factory.instance().createAddress(identity);
AuthInfo authInfo = Factory.instance().createAuthInfo(address.getUsername(), "", password, "", "",
address.getDomain());
core.addAuthInfo(authInfo);
AccountParams accountParams = core.createAccountParams();
accountParams.setIdentityAddress(address);
Address serverAddress = Factory.instance().createAddress("sip:" + address.getDomain() + ";transport=tls");
accountParams.setServerAddress(serverAddress);
accountParams.setRegisterEnabled(true);
Account account = core.createAccount(accountParams);
core.addAccount(account);
core.setDefaultAccount(account);
}
public void logout() {
Account account = core.getDefaultAccount();
if (account != null) {
AccountParams accountParams = account.getParams().clone();
accountParams.setRegisterEnabled(false);
account.setParams(accountParams);
}
}
// Make an outgoing call.
public void call(String uriToCall) {
// We create an Address object from the URI.
// This method can create a SIP Address from a username or phone number only.
Address address = core.interpretUrl(uriToCall);
// Initiate an outgoing call to the given destination Address.
core.inviteAddress(address);
}
public Boolean toggleCamera() {
Call call = core.getCurrentCall();
CallParams params = core.createCallParams(call);
Boolean newValue = !params.isVideoEnabled();
params.setVideoEnabled(newValue);
call.update(params);
return newValue;
}
public Boolean toggleMicrophone() {
core.setMicEnabled(!core.isMicEnabled());
return core.isMicEnabled();
}
public Boolean toggleSpeaker() {
core.getCurrentCall().setSpeakerMuted(!core.getCurrentCall().getSpeakerMuted());
return core.getCurrentCall().getSpeakerMuted();
}
public void clearCoreAfterLogout() {
core.clearAllAuthInfo();
core.clearAccounts();
}
@Override
public void onGlobalStateChanged(Core core, GlobalState state, String message) {
if (state == GlobalState.Off) {
iterateTimer.cancel();
}
}
}
class IterateTimerTask extends TimerTask {
@Override
public void run() {
Platform.runLater(CoreService.instance().iterateRunnable);
}
}
class IterateRunnable implements Runnable {
@Override
public void run() {
CoreService.instance().core.iterate();
}
}

View File

@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<TitledPane fx:id="callPane" alignment="TOP_CENTER" animated="false" collapsible="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" text="Hello" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1" fx:controller="controller.CallController">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0">
<children>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label alignment="CENTER" text="URI to call:" GridPane.halignment="CENTER" />
<TextField fx:id="uriToCallField" maxWidth="350.0" minWidth="350.0" prefWidth="350.0" promptText="sip:" GridPane.columnIndex="1" GridPane.halignment="CENTER" />
</children>
</GridPane>
<Button fx:id="callButton" mnemonicParsing="false" onMouseClicked="#onCallClicked" text="Call">
<VBox.margin>
<Insets bottom="30.0" top="20.0" />
</VBox.margin>
</Button>
<Label fx:id="callLabel" alignment="CENTER" text="Your call state is: Idle" />
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="35.0">
<children>
<Button fx:id="hangupButton" disable="true" mnemonicParsing="false" onMouseClicked="#onHangUpClicked" text="Hang up" />
<Button fx:id="cameraButton" disable="true" mnemonicParsing="false" onMouseClicked="#onCameraClicked" text="Activate camera" />
<Button fx:id="muteSoundButton" disable="true" mnemonicParsing="false" onMouseClicked="#onMuteSoundClicked" text="Mute sound" />
<Button fx:id="muteMicrophoneButton" disable="true" mnemonicParsing="false" onMouseClicked="#onMuteMicrophoneClicked" text="Mute microphone" />
</children>
</HBox>
</children>
<VBox.margin>
<Insets top="20.0" />
</VBox.margin>
</VBox>
<VBox fx:id="incomingCallVBox" alignment="CENTER" prefHeight="200.0" prefWidth="100.0" visible="false">
<children>
<Label text="You have a call from:" />
<Label fx:id="incomingCallLabel" />
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="35.0">
<children>
<Button fx:id="answerButton" mnemonicParsing="false" onMouseClicked="#onAnswerClicked" text="Answer" />
<Button fx:id="declineButton" mnemonicParsing="false" onMouseClicked="#onDeclineClicked" text="Decline" />
</children>
</HBox>
</children>
</VBox>
</children>
</VBox>
</children></AnchorPane>
</content>
</TitledPane>

View File

@@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.FlowPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<TitledPane alignment="TOP_CENTER" animated="false" collapsible="false" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" text="Login Form" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/17" fx:controller="controller.LoginController">
<content>
<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0">
<children>
<FlowPane alignment="TOP_CENTER" columnHalignment="CENTER" layoutX="199.0" layoutY="54.0" orientation="VERTICAL" prefHeight="200.0" prefWidth="200.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="350.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="Identity:" />
<TextField fx:id="identityField" maxWidth="350.0" minWidth="350.0" prefWidth="350.0" GridPane.columnIndex="1" />
<Label text="Password:" GridPane.rowIndex="1" />
<PasswordField fx:id="passwordField" maxWidth="350.0" minWidth="350.0" prefWidth="350.0" GridPane.columnIndex="1" GridPane.rowIndex="1" />
</children>
<FlowPane.margin>
<Insets bottom="20.0" top="20.0" />
</FlowPane.margin>
</GridPane>
<Label fx:id="registrationLabel" prefWidth="0.0">
<FlowPane.margin>
<Insets />
</FlowPane.margin>
</Label>
<Separator prefWidth="200.0" />
<ButtonBar prefHeight="40.0" prefWidth="200.0">
<buttons>
<Button fx:id="loginButton" mnemonicParsing="false" onMouseClicked="#onLoginClicked" text="Login" />
</buttons>
</ButtonBar>
</children>
</FlowPane>
</children></AnchorPane>
</content>
</TitledPane>

View File

@@ -1,11 +0,0 @@
Java tutorials
====================
Tutorials are numbered 0 to 3, and we recommend you to read them in that order as features from previous tutorials are used in the next ones, such as account login.
Each tutorial is a full project, so you should build it and run it independently.
For each tutorial, you need to extract the Linphone Java SDK zip file in the tutorial directory and run `./gradlew run` to execute it.
Code is being kept as short and simple as possible, and comments explain how and why things are being done.
Full Java API is available [here](http://linphone.org/snapshots/docs/liblinphone/latest/java).

View File

@@ -1,13 +0,0 @@
indent_style = tabs
indent_size = 4
[*.cs]
# IDE0008: Use explicit type
csharp_style_var_when_type_is_apparent = true
# IDE0008: Use explicit type
csharp_style_var_for_built_in_types = true
# IDE0008: Use explicit type
csharp_style_var_elsewhere = true

View File

@@ -151,16 +151,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="LinphoneSDK">
<Version>5.1.0</Version>
<Version>5.1.0-alpha.56</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
<Version>6.2.11</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig">
<Link>.editorconfig</Link>
</None>
<None Include="Readme.md" />
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">

View File

@@ -63,16 +63,16 @@ namespace _00_HelloWorld
factory.MspluginsDir = ".";
// Your Core can use up to 2 configuration files, but that isn't mandatory.
// The third parameter is the application context, which is *not* mandatory when working
// with UWP, but *is* mandatory in an Android context for example.
// The third parameter is the application context, he isn't mandatory when working
// with UWP, he is mandatory in an Android context for example.
// You can now create your Core object :
Core core = factory.CreateCore("", "", IntPtr.Zero);
// Once you have your core you can start to do a lot of things.
// Once you got your core you can start to do a lot of things.
HelloText += Core.Version;
// You should store the Core to keep a reference to it at all times while your app is alive.
// A good solution for that is to either subclass the Application object or create a Service.
// You should store the Core to keep a reference on it at all times while your app is alive.
// A good solution for that is either subclass the Application object or create a Service.
StoredCore = core;
}

View File

@@ -1,15 +1,19 @@
Linphone X UWP tutorial 00_HelloWorld
======================================
The first tutorial is just a hello world app displaying the current SDK version number.
The first tutorial is just here to display a hello world app with the current Linphone's version number.
Don't forget to install those NuGet packages :
- LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
- Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
Main files :
```
00_HelloWorld
│ README.md : you are here
│ README.md : you are here
│ App.xaml(.cs) : Default Windows Application file, nothing special here
│ MainPage.xaml(.cs) : This is were the magic happens,
│ jump into this file to learn the basics of how to setup your app to use Linphone by creating the Core object.
│ MainPage.xaml(.cs) : This is were the magic happen,
│ jump into this file to learn about Linphone core creation and how to display a hello world.
└───Assets : default UWP app assets
│ LockScreenLogo.scale-200.png

View File

@@ -151,10 +151,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="LinphoneSDK">
<Version>5.1.0</Version>
<Version>5.1.0-alpha.56</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
<Version>6.2.11</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -66,18 +66,17 @@ namespace _01_AccountLogin
StoredCore = core;
// We need to indicate to the core where to find the root and user certificates, for future TLS exchange.
// We need to indicate to the core where are stored the root ans user certificates, for future TLS exchange.
StoredCore.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
StoredCore.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
// In this tutorial we are going to log in and our registration state will change.
// In this tutorials we are going to log in and our registration state will change.
// Here we show you how to register a delegate method called every time the
// OnAccountRegistrationStateChanged callback is triggered.
// on OnAccountRegistrationStateChanged callback is triggered.
StoredCore.Listener.OnAccountRegistrationStateChanged += OnAccountRegistrationStateChanged;
// Start the core after setup, and before everything else.
StoredCore.Start();
StoredCore.AutoIterateEnabled = true;
// The method Iterate must be permanently called on our core.
// The Iterate method runs all the waiting backgrounds tasks and poll networks notifications.
@@ -136,17 +135,7 @@ namespace _01_AccountLogin
// We also need to configure where the proxy server is located
Address serverAddr = Factory.Instance.CreateAddress("sip:" + address.Domain);
// We use the Address object to easily set the transport protocol
if (TlsRadio.IsChecked == true) {
serverAddr.Transport = TransportType.Tls;
}
else if (TcpRadio.IsChecked == true)
{
serverAddr.Transport = TransportType.Tcp;
}
else
{
serverAddr.Transport = TransportType.Udp;
}
serverAddr.Transport = TlsRadio.IsChecked ?? false ? TransportType.Tls : TcpRadio.IsChecked ?? false ? TransportType.Tcp : TransportType.Udp;
accountParams.ServerAddress = serverAddr;
// If RegisterEnabled is set to true, when this account will be added to the core it will
// automatically try to connect.

View File

@@ -1,13 +1,17 @@
Linphone X UWP tutorial 01_AccountLogin
================================
This project will walk you through the different steps of logging in and out of a SIP account.
In this tutorial we present you the different steps to login and logout a SIP account.
If you do not yet have a SIP account, please create one here : https://www.linphone.org/freesip/home
To first register an account go here : https://www.linphone.org/freesip/home
Don't forget to install those NuGet packages :
- LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
- Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
New/updated files to watch :
```
01_AccountLogin
│ MainPage.xaml(.cs) : This was changed to a minimalist login page and displays your login status.
take a look at the code to understand how to login/out with LinphoneSDK.
│ MainPage.xaml(.cs) : This time this page is a minimalist login page and it display your login status.
Watch its code to understand how to login/out with LinphoneSDK.
```

View File

@@ -169,10 +169,10 @@
<Version>2.1.13</Version>
</PackageReference>
<PackageReference Include="LinphoneSDK">
<Version>5.1.0</Version>
<Version>5.1.0-alpha.56</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
<Version>6.2.11</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -3,16 +3,20 @@
This time we are going to receive our first calls !
The architecture of the first two tutorials was a bit simple for a larger app, so we moved things a bit.
All the core-related code (creation, iterate, log in...) is now in the class Service/CoreService.
Because the architecture of the first two tutorials were a bit too simple for a larger app we moved things a bit.
All the code about the core (creation, iterate, log in...) is now in the class Service/CoreService.
The LoginPage now redirects to a new page (NavigationRoot) this page only contains a NavigationView.
If you are unfamiliar with NavigationView you can take a look at [the NavigationView doc](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/navigationview),
The page LoginPage is updated and now redirects to a new page (NavigationRoot) this page only contains a NavigationView.
If you are note familiar with NavigationView you can take a look at [the NavigationView doc](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/navigationview),
but this is not mandatory since it contains no Linphone code and is only here for navigation.
By default the NavigationView loads the new CallsPage (the only one for now), on this page you can answer or decline incoming calls.
By default the NavigationView load the new page CallsPage (the only one for now), on this page you can answer or decline incoming calls.
If you don't have SIP friends to test with, you can also install Linphone on your mobile device (Android or iOS) and call yourself with a different account.
If you don't have SIP friends to make tests we recommend you to install Linphone on your mobile device (Android or iOS) and to make calls to yourself.
Don't forget to install those NuGet packages :
- LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
- Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
New/updated files :
@@ -29,10 +33,10 @@ New/updated files :
└───Views :
│ │ CallsPage.xaml(.cs) : This is the new page from which you can make calls.
│ │ Also contains new Linphone-related code.
│ │ CallsPage.xaml(.cs) : This is the new page where you can make calls.
│ │ This is where you will find the new Linphone's uses.
│ │
│ │ LoginPage.xaml(.cs) : The same login page as the previous step, now in its own file.
│ │ LoginPage.xaml(.cs) : The same login page as the previous step, now in his own file.
│ │
│ │ NavigationRoot.xaml(.cs) : The new page containing the NavigationView and the main app Frame.

View File

@@ -148,9 +148,9 @@ namespace _02_IncomingCall.Service
/// <summary>
/// Mute/Unmute your microphone.
/// Setting MicEnabled=false on the Core mutes your microphone globally.
/// Set MicEnabled=false on the Core mute your microphone globally.
/// </summary>
public bool ToggleMic()
public bool MicEnabledSwitch()
{
// The following toggles the microphone, disabling completely / enabling the sound capture from the device microphone
return Core.MicEnabled = !Core.MicEnabled;
@@ -158,9 +158,9 @@ namespace _02_IncomingCall.Service
/// <summary>
/// Enable/Disable the speaker sound.
/// Setting SpeakerMuted=true on a Call object disables the sound output of this call.
/// Set SpeakerMuted=true on a Call object to disable the sound of this call.
/// </summary>
public bool ToggleSpeaker()
public bool SpeakerMutedSwitch()
{
return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
}

View File

@@ -25,7 +25,7 @@
<TextBlock x:Name="CallText" Text="Your call state is : Idle" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10">
<Button x:Name="HangUp" Content="Hang up" Click="OnHangUpClicked" IsEnabled="False" />
<Button x:Name="HangOut" Content="Hang out" Click="HangOutClick" IsEnabled="False" />
<Button x:Name="Sound" Content="Switch off Sound" Click="SoundClick" IsEnabled="False" />
<Button x:Name="Mic" Content="Mute" Click="MicClick" IsEnabled="False" />
</StackPanel>
@@ -34,7 +34,7 @@
<StackPanel Grid.Row="1" x:Name="IncomingCallStackPanel" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Collapsed" Margin="10">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="You have a call from :" />
<TextBlock x:Name="IncomingCallText" Text="" />
<TextBlock x:Name="IncommingCallText" Text="" />
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="Answer" Content="Answer" Click="AnswerClick" />

View File

@@ -29,7 +29,7 @@ namespace _02_IncomingCall.Views
{
private CoreService CoreService { get; } = CoreService.Instance;
private Call IncomingCall;
private Call IncommingCall;
public CallsPage()
{
@@ -50,9 +50,9 @@ namespace _02_IncomingCall.Views
HelloText.Text += CoreService.Core.DefaultProxyConfig.FindAuthInfo().Username;
// On each stage of a call we want to update our GUI.
// The same way we did for OnAccountRegistrationStateChanged we can register
// The same way we did it for OnAccountRegistrationStateChanged we can register
// a delegate called every time the state of a call changed.
// See this.OnCallStateChanged for more details
// Watch this.OnCallStateChanged for more details
CoreService.AddOnCallStateChangedDelegate(OnCallStateChanged);
if (CoreService.Core.CurrentCall != null)
@@ -64,7 +64,7 @@ namespace _02_IncomingCall.Views
/// <summary>
/// Method called when the "Hang out" button is clicked.
/// </summary>
private void OnHangUpClicked(object sender, RoutedEventArgs e)
private void HangOutClick(object sender, RoutedEventArgs e)
{
// Simply call TerminateAllCalls to hang out.
// You could also do something like CoreService.Core.CurrentCall?.Terminate();
@@ -73,11 +73,11 @@ namespace _02_IncomingCall.Views
/// <summary>
/// Method called when the "Switch on/off" button is clicked.
/// See CoreService.ToggleSpeaker for more info.
/// Watch CoreService.SpeakerMutedSwitch for more info.
/// </summary>
private void SoundClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleSpeaker())
if (CoreService.SpeakerMutedSwitch())
{
Sound.Content = "Switch on Sound";
}
@@ -89,11 +89,11 @@ namespace _02_IncomingCall.Views
/// <summary>
/// Method to mute/unmute your microphone.
/// See CoreService.ToggleMic for more info.
/// Watch CoreService.MicEnabledSwitch for more info.
/// </summary>
private void MicClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleMic())
if (CoreService.MicEnabledSwitch())
{
Mic.Content = "Mute";
}
@@ -119,10 +119,10 @@ namespace _02_IncomingCall.Views
// "Busy". If you want to implement a multi call app you can increase Core.MaxCalls.
// Here we store the incoming call reference so we can accept or decline the call on user input, see AnswerClick
// and DeclineClick.
IncomingCall = call;
IncommingCall = call;
// And we update the GUI to notify the user of the incoming call.
IncomingCallStackPanel.Visibility = Visibility.Visible;
IncomingCallText.Text = " " + IncomingCall.RemoteAddress.AsString();
IncommingCallText.Text = " " + IncommingCall.RemoteAddress.AsString();
break;
@@ -136,7 +136,7 @@ namespace _02_IncomingCall.Views
case CallState.Released:
// By default after 30 seconds of ringing without accept or decline a call is
// automatically ended.
IncomingCall = null;
IncommingCall = null;
EndingCallGuiUpdates();
break;
@@ -148,7 +148,7 @@ namespace _02_IncomingCall.Views
/// </summary>
private async void AnswerClick(object sender, RoutedEventArgs e)
{
if (IncomingCall != null)
if (IncommingCall != null)
{
// We call this method to pop the microphone permission window.
// If the permission was already granted for this app, no pop up
@@ -157,8 +157,8 @@ namespace _02_IncomingCall.Views
// To accept a call only use the Accept() method on the call object.
// If we wanted, we could create a CallParams object and answer using this object to make changes to the call configuration.
IncomingCall.Accept();
IncomingCall = null;
IncommingCall.Accept();
IncommingCall = null;
}
}
@@ -167,12 +167,12 @@ namespace _02_IncomingCall.Views
/// </summary>
private void DeclineClick(object sender, RoutedEventArgs e)
{
if (IncomingCall != null)
if (IncommingCall != null)
{
// You have to give a Reason to decline a call. This info is sent to the remote.
// You have do give a Reason to decline a call. This info is sent to the remote.
// See Linphone.Reason to see the full list.
IncomingCall.Decline(Reason.Declined);
IncomingCall = null;
IncommingCall.Decline(Reason.Declined);
IncommingCall = null;
}
}
@@ -182,7 +182,7 @@ namespace _02_IncomingCall.Views
private void EndingCallGuiUpdates()
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
HangUp.IsEnabled = false;
HangOut.IsEnabled = false;
Sound.IsEnabled = false;
Mic.IsEnabled = false;
Mic.Content = "Mute";
@@ -195,7 +195,7 @@ namespace _02_IncomingCall.Views
private void CallInProgressGuiUpdates()
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
HangUp.IsEnabled = true;
HangOut.IsEnabled = true;
Sound.IsEnabled = true;
Mic.IsEnabled = true;
}

View File

@@ -52,7 +52,7 @@ namespace _02_IncomingCall.Views
{
switch (e.SourcePageType)
{
case Type _ when e.SourcePageType == typeof(CallsPage):
case Type c when e.SourcePageType == typeof(CallsPage):
((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
break;
}
@@ -65,17 +65,18 @@ namespace _02_IncomingCall.Views
ContentDialog noSettingsDialog = new ContentDialog
{
Title = "No settings",
Content = "There are no settings in this little app",
Content = "There is no settings in this little app",
CloseButtonText = "OK"
};
_ = await noSettingsDialog.ShowAsync();
ContentDialogResult result = await noSettingsDialog.ShowAsync();
return;
}
if (args.InvokedItem is string invokedItemValue && invokedItemValue.Contains("Calls"))
string invokedItemValue = args.InvokedItem as string;
if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
{
_ = AppNavFrame.Navigate(typeof(CallsPage));
AppNavFrame.Navigate(typeof(CallsPage));
}
}
@@ -89,7 +90,7 @@ namespace _02_IncomingCall.Views
ContentDialog signOutDialog = new ContentDialog
{
Title = "Sign out ?",
Content = "All your current calls and actions will be canceled.",
Content = "All your current calls and actions will be canceled, are you sure to continue ?",
PrimaryButtonText = "Sign out",
CloseButtonText = "Cancel"
};

View File

@@ -170,10 +170,10 @@
<Version>2.1.13</Version>
</PackageReference>
<PackageReference Include="LinphoneSDK">
<Version>5.1.0</Version>
<Version>5.1.0-alpha.56</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
<Version>6.2.11</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -3,10 +3,6 @@
This time we are going to make our first video calls.
Note the new ANGLE.WindowsStore package that was added. This is required for video rendering.
(If you restored NuGet packages for the solution as indicated in the parent Readme, it should
already be installed, and no additional action is needed on your side.)
New/updated files :
```
@@ -18,10 +14,10 @@ New/updated files :
│ │ Now updated with the ability to make video calls.
│ │
│ │ VideoService.cs : A singleton service which contains the code to render the video call
│ │ on a SwapChainPanel, using OpenGL.
│ │ on SwapChainPanel, using OpenGL.
└───Views :
│ │ CallsPage.xaml(.cs) : This is the page where you can make calls.
│ │ Also contains new Linphone-related code.
│ │ This is where you will find the new Linphone's uses.
```

View File

@@ -70,13 +70,15 @@ namespace _03_OutgoingCall.Service
core.RootCa = Path.Combine(Windows.ApplicationModel.Package.Current.InstalledLocation.Path, "share", "Linphone", "rootca.pem");
core.UserCertificatesPath = ApplicationData.Current.LocalFolder.Path;
// NEW!
VideoActivationPolicy videoActivationPolicy = factory.CreateVideoActivationPolicy();
videoActivationPolicy.AutomaticallyAccept = true;
videoActivationPolicy.AutomaticallyInitiate = false;
core.VideoActivationPolicy = videoActivationPolicy;
core.VideoCaptureEnabled = core.VideoSupported();
if (core.VideoSupported())
{
core.VideoCaptureEnabled = true;
}
core.UsePreviewWindow(true);
}
return core;
@@ -172,12 +174,12 @@ namespace _03_OutgoingCall.Service
Core.InviteAddress(address);
}
public bool ToggleMic()
public bool MicEnabledSwitch()
{
return Core.MicEnabled = !Core.MicEnabled;
}
public bool ToggleSpeaker()
public bool SpeakerMutedSwitch()
{
return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
}
@@ -185,8 +187,11 @@ namespace _03_OutgoingCall.Service
/// <summary>
/// Ask the peer of the current call to enable/disable the video call.
/// </summary>
public async Task<bool> ToggleCameraAsync()
public async Task<bool> CameraEnabledSwitchAsync()
{
// We call this method to pop up the webcam permission window.
// If the permission was already granted for this app, no pop up
// appears.
await OpenCameraPopup();
// Retrieving the current call
@@ -194,25 +199,24 @@ namespace _03_OutgoingCall.Service
// Core.createCallParams(call) create CallParams matching the Call parameters,
// here the current call. CallParams contains a variety of parameters like
// audio bandwidth limit, media encryption type...< And if the video is enable
// audio bandwidth limit, media encryption type... And if the video is enable
// or not.
CallParams param = core.CreateCallParams(call);
// Switch the current VideoEnableValue
bool newValue = !param.VideoEnabled;
param.VideoEnabled = newValue;
param.VideoDirection = MediaDirection.RecvOnly;
// Try to update the call parameters with those new CallParams.
// If the video switched from true to false the peer can't refuse to disable the video.
// If the video switched from false to true and the peer doesn't have videoActivationPolicy.AutomaticallyAccept = true
// you have to wait for them to accept the update. The Call status is "Updating" during this time.
// If the video switch from true to false the peer can't refuse to disable the video.
// If the video switch from false to true and the peer don't have videoActivationPolicy.AutomaticallyAccept = true
// you have to wait for him to accept the update. The Call status is "Updating" during this time.
call.Update(param);
return newValue;
}
public async Task OpenMicrophonePopup()
private async Task OpenMicrophonePopup()
{
AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
@@ -227,18 +231,11 @@ namespace _03_OutgoingCall.Service
private async Task OpenCameraPopup()
{
MediaCapture mediaCapture = new MediaCapture();
try
MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
{
await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
{
StreamingCaptureMode = StreamingCaptureMode.Video
});
}
catch (Exception e) when(e.Message.StartsWith("No capture devices are available."))
{
// Ignored. You can ask the remote party for video even if you don't have a camera.
}
StreamingCaptureMode = StreamingCaptureMode.Video
});
mediaCapture.Dispose();
}
}

View File

@@ -39,7 +39,7 @@ namespace _03_OutgoingCall.Service
/// When you want to start the video rendering you need to link the SwapChainPanel surface to Linphone.
/// Core.NativePreviewWindowId for the preview surface and Core.CurrentCall.NativeVideoWindowId for the
/// remote webcam surface.
/// Simply doing this allows Linphone to render your preview and the remote camera if they are available.
/// Simply doing this allow Linphone to render your preview and the remote camera if they are available.
/// </summary>
public void StartVideoStream(SwapChainPanel main, SwapChainPanel preview)
{

View File

@@ -32,7 +32,7 @@
<TextBlock x:Name="CallText" Text="Your call state is : Idle" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10">
<Button x:Name="HangUp" Content="Hang up" Click="OnHangUpClicked" IsEnabled="False" />
<Button x:Name="HangOut" Content="Hang out" Click="HangOutClick" IsEnabled="False" />
<Button x:Name="Sound" Content="Switch off Sound" Click="SoundClick" IsEnabled="False" />
<Button x:Name="Camera" Content="Switch on Camera" Click="CameraClick" IsEnabled="False" />
<Button x:Name="Mic" Content="Mute" Click="MicClick" IsEnabled="False" />

View File

@@ -31,7 +31,7 @@ namespace _03_OutgoingCall.Views
private VideoService VideoService { get; } = VideoService.Instance;
private Call IncomingCall;
private Call IncommingCall;
public CallsPage()
{
@@ -71,14 +71,14 @@ namespace _03_OutgoingCall.Views
CoreService.Call(UriToCall.Text);
}
private void OnHangUpClicked(object sender, RoutedEventArgs e)
private void HangOutClick(object sender, RoutedEventArgs e)
{
CoreService.Core.TerminateAllCalls();
}
private void SoundClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleSpeaker())
if (CoreService.SpeakerMutedSwitch())
{
Sound.Content = "Switch on Sound";
}
@@ -90,22 +90,22 @@ namespace _03_OutgoingCall.Views
/// <summary>
/// Method to turn on/off the video call.
/// See CoreService.ToggleCameraAsync for more info.
/// Watch CoreService.CameraEnabledSwitchAsync for more info.
/// </summary>
private async void CameraClick(object sender, RoutedEventArgs e)
{
await CoreService.ToggleCameraAsync();
await CoreService.CameraEnabledSwitchAsync();
// After CoreService.ToggleCameraAsync the Call state is "Updating".
// After CoreService.CameraEnabledSwitchAsync the Call state is "Updating".
// We wait for the return of the "StreamsRunning" state to update the GUI
// according to the final consensus between callers.
Camera.Content = "Waiting for remote party to accept ...";
Camera.Content = "Waiting for accept ...";
Camera.IsEnabled = false;
}
private void MicClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleMic())
if (CoreService.MicEnabledSwitch())
{
Mic.Content = "Mute";
}
@@ -121,24 +121,24 @@ namespace _03_OutgoingCall.Views
switch (state)
{
case CallState.IncomingReceived:
IncomingCall = call;
IncommingCall = call;
IncomingCallStackPanel.Visibility = Visibility.Visible;
IncommingCallText.Text = " " + IncomingCall.RemoteAddress.AsString();
IncommingCallText.Text = " " + IncommingCall.RemoteAddress.AsString();
break;
// The different states a call goes through before your peer answers.
case CallState.OutgoingInit:
case CallState.OutgoingProgress:
case CallState.OutgoingRinging:
HangUp.IsEnabled = true;
// Different states you go through when you start a call and before your peer answer.
HangOut.IsEnabled = true;
break;
// The StreamsRunning state is the default one during a call.
case CallState.StreamsRunning:
// The UpdatedByRemote state is triggered when the call's parameters are updated
// for example when video is asked/removed by remote.
case CallState.UpdatedByRemote:
// The StreamsRunning state is the default one during a call.
// The UpdatedByRemote is triggered when the call's parameters are updated
// for example when video is asked/removed by remote.
CallInProgressGuiUpdates();
if (call.CurrentParams.VideoEnabled)
@@ -154,7 +154,7 @@ namespace _03_OutgoingCall.Views
case CallState.Error:
case CallState.End:
case CallState.Released:
IncomingCall = null;
IncommingCall = null;
EndingCallGuiUpdates();
VideoService.StopVideoStream();
@@ -162,22 +162,21 @@ namespace _03_OutgoingCall.Views
}
}
private async void AnswerClick(object sender, RoutedEventArgs e)
private void AnswerClick(object sender, RoutedEventArgs e)
{
if (IncomingCall != null)
if (IncommingCall != null)
{
await CoreService.OpenMicrophonePopup();
IncomingCall.Accept();
IncomingCall = null;
IncommingCall.Accept();
IncommingCall = null;
}
}
private void DeclineClick(object sender, RoutedEventArgs e)
{
if (IncomingCall != null)
if (IncommingCall != null)
{
IncomingCall.Decline(Reason.Declined);
IncomingCall = null;
IncommingCall.Decline(Reason.Declined);
IncommingCall = null;
}
}
@@ -195,7 +194,7 @@ namespace _03_OutgoingCall.Views
/// <summary>
/// Method to show the webcam grid and start rendering remote and preview webcam.
/// See VideoService and more specifically VideoService.StartVideoStream to
/// Watch VideoService and more specifically VideoService.StartVideoStream to
/// understand how to start the rendering on a SwapChainPanel.
/// </summary>
private void StartVideoAndUpdateGui()
@@ -210,7 +209,7 @@ namespace _03_OutgoingCall.Views
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
CallButton.IsEnabled = true;
HangUp.IsEnabled = false;
HangOut.IsEnabled = false;
Sound.IsEnabled = false;
Camera.IsEnabled = false;
Mic.IsEnabled = false;
@@ -224,7 +223,7 @@ namespace _03_OutgoingCall.Views
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
CallButton.IsEnabled = false;
HangUp.IsEnabled = true;
HangOut.IsEnabled = true;
Sound.IsEnabled = true;
Camera.IsEnabled = true;
Mic.IsEnabled = true;

View File

@@ -26,9 +26,6 @@ using Windows.UI.Xaml.Navigation;
namespace _03_OutgoingCall.Views
{
/// <summary>
/// Introduced in step 02 IncomingCall
/// </summary>
public sealed partial class NavigationRoot : Page
{
private CoreService CoreService { get; } = CoreService.Instance;
@@ -41,6 +38,9 @@ namespace _03_OutgoingCall.Views
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Only do an inital navigate the first time the page loads
// when we switch out of compactoverloadmode this will fire but we don't want to navigate because
// there is already a page loaded
if (!hasLoadedPreviously)
{
AppNavFrame.Navigate(typeof(CallsPage));
@@ -52,7 +52,7 @@ namespace _03_OutgoingCall.Views
{
switch (e.SourcePageType)
{
case Type _ when e.SourcePageType == typeof(CallsPage):
case Type c when e.SourcePageType == typeof(CallsPage):
((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
break;
}
@@ -65,16 +65,18 @@ namespace _03_OutgoingCall.Views
ContentDialog noSettingsDialog = new ContentDialog
{
Title = "No settings",
Content = "There are no settings in this little app",
Content = "There is no settings in this little app",
CloseButtonText = "OK"
};
_ = await noSettingsDialog.ShowAsync();
ContentDialogResult result = await noSettingsDialog.ShowAsync();
return;
}
if (args.InvokedItem is string invokedItemValue && invokedItemValue.Contains("Calls"))
string invokedItemValue = args.InvokedItem as string;
if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
{
_ = AppNavFrame.Navigate(typeof(CallsPage));
AppNavFrame.Navigate(typeof(CallsPage));
}
}
@@ -88,7 +90,7 @@ namespace _03_OutgoingCall.Views
ContentDialog signOutDialog = new ContentDialog
{
Title = "Sign out ?",
Content = "All your current calls and actions will be canceled.",
Content = "All your current calls and actions will be canceled, are you sure to continue ?",
PrimaryButtonText = "Sign out",
CloseButtonText = "Cancel"
};

View File

@@ -185,10 +185,10 @@
<Version>2.1.13</Version>
</PackageReference>
<PackageReference Include="LinphoneSDK">
<Version>5.1.0</Version>
<Version>5.1.0-alpha.56</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
<Version>6.2.11</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -5,7 +5,12 @@ Second big step in this tutorial, we can now communicate in basic chat rooms.
In this part you are going to learn how to send and receive text messages over SIP using LinphoneSDK.
For our first step with ChatRoom we are going to create only one to one basic ChatRoom (no encryption, no ephemeral),
and for now the tutorial app will only support text messages.
and for now the tutorial app will only support text message.
Don't forget to install those NuGet packages :
- LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
- Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
- ANGLE.WindowsStore (for video rendering, version 2.1.13 recommended)
New/Updated files :
@@ -15,16 +20,16 @@ New/Updated files :
│ │ CoreService.cs : A singleton service which contains the Linphone.Core.
│ │ We added some code to create new chat rooms here.
│ │
│ │ NavigationService.cs : A small service used to keep references to pages currently displayed.
│ │ NavigationService.cs : A small service used to keeps reference to current pages displayed.
└───Views :
│ │
│ │ ChatPage.xaml(.cs) : This is the frame displayed when you select a chat room.
│ │ For now it's a basic page where you can send messages and see your
│ │ For now it's a simple page where you can send message and see your
│ │ conversation history.
│ │
│ │ ChatsPage.xaml(.cs) : In this page we list all the existing chat rooms. When you select
│ │ one of them a ChatPage is rendered. You can also create new ChatRoom here.
│ │ ChatsPage.xaml(.cs) : In this page we list all the existing chat rooms. If you select
│ │ one of them a ChatPage is render. You can also create new ChatRoom here.
│ │
│ │ NavigationRoot.xaml(.cs) : The navigation page, you can now navigate to the ChatsPage !
│ │ NavigationRoot.xaml(.cs) : The navigation page, you can now navigate the ChatsPage !
```

View File

@@ -75,8 +75,11 @@ namespace _04_BasicChat.Service
videoActivationPolicy.AutomaticallyInitiate = false;
core.VideoActivationPolicy = videoActivationPolicy;
core.VideoCaptureEnabled = core.VideoSupported();
if (core.VideoSupported())
{
core.VideoDisplayFilter = "MSOGL";
core.VideoCaptureEnabled = true;
}
core.UsePreviewWindow(true);
}
return core;
@@ -189,17 +192,17 @@ namespace _04_BasicChat.Service
Core.InviteAddress(address);
}
public bool ToggleMic()
public bool MicEnabledSwitch()
{
return Core.MicEnabled = !Core.MicEnabled;
}
public bool ToggleSpeaker()
public bool SpeakerMutedSwitch()
{
return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
}
public async Task<bool> ToggleCameraAsync()
public async Task<bool> CameraEnabledSwitchAsync()
{
await OpenCameraPopup();
@@ -253,7 +256,7 @@ namespace _04_BasicChat.Service
return Core.CreateChatRoom(chatRoomParams, localAdress, new[] { remoteAddress });
}
public async Task OpenMicrophonePopup()
private async Task OpenMicrophonePopup()
{
AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
@@ -268,18 +271,11 @@ namespace _04_BasicChat.Service
private async Task OpenCameraPopup()
{
MediaCapture mediaCapture = new MediaCapture();
try
MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
{
await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
{
StreamingCaptureMode = StreamingCaptureMode.Video
});
}
catch (Exception e) when (e.Message.StartsWith("No capture devices are available."))
{
// Ignored.
}
StreamingCaptureMode = StreamingCaptureMode.Video
});
mediaCapture.Dispose();
}
}

View File

@@ -32,7 +32,7 @@
<TextBlock x:Name="CallText" Text="Your call state is : Idle" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10">
<Button x:Name="HangUp" Content="Hang up" Click="OnHangUpClicked" IsEnabled="False" />
<Button x:Name="HangOut" Content="Hang out" Click="HangOutClick" IsEnabled="False" />
<Button x:Name="Sound" Content="Switch off Sound" Click="SoundClick" IsEnabled="False" />
<Button x:Name="Camera" Content="Switch on Camera" Click="CameraClick" IsEnabled="False" />
<Button x:Name="Mic" Content="Mute" Click="MicClick" IsEnabled="False" />

View File

@@ -31,7 +31,7 @@ namespace _04_BasicChat.Views
private VideoService VideoService { get; } = VideoService.Instance;
private Call IncomingCall;
private Call IncommingCall;
public CallsPage()
{
@@ -63,14 +63,14 @@ namespace _04_BasicChat.Views
CoreService.Call(UriToCall.Text);
}
private void OnHangUpClicked(object sender, RoutedEventArgs e)
private void HangOutClick(object sender, RoutedEventArgs e)
{
CoreService.Core.TerminateAllCalls();
}
private void SoundClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleSpeaker())
if (CoreService.SpeakerMutedSwitch())
{
Sound.Content = "Switch on Sound";
}
@@ -82,14 +82,14 @@ namespace _04_BasicChat.Views
private async void CameraClick(object sender, RoutedEventArgs e)
{
await CoreService.ToggleCameraAsync();
await CoreService.CameraEnabledSwitchAsync();
Camera.Content = "Waiting for accept ...";
Camera.IsEnabled = false;
}
private void MicClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleMic())
if (CoreService.MicEnabledSwitch())
{
Mic.Content = "Mute";
}
@@ -99,19 +99,18 @@ namespace _04_BasicChat.Views
}
}
private async void AnswerClick(object sender, RoutedEventArgs e)
private void AnswerClick(object sender, RoutedEventArgs e)
{
await CoreService.OpenMicrophonePopup();
IncomingCall.Accept();
IncomingCall = null;
IncommingCall.Accept();
IncommingCall = null;
}
private void DeclineClick(object sender, RoutedEventArgs e)
{
if (IncomingCall != null)
if (IncommingCall != null)
{
IncomingCall.Decline(Reason.Declined);
IncomingCall = null;
IncommingCall.Decline(Reason.Declined);
IncommingCall = null;
}
}
@@ -122,7 +121,7 @@ namespace _04_BasicChat.Views
{
case CallState.IncomingReceived:
IncomingCall = call;
IncommingCall = call;
IncomingCallStackPanel.Visibility = Visibility.Visible;
IncommingCallText.Text = " " + call.RemoteAddress.AsString();
break;
@@ -131,7 +130,7 @@ namespace _04_BasicChat.Views
case CallState.OutgoingProgress:
case CallState.OutgoingRinging:
HangUp.IsEnabled = true;
HangOut.IsEnabled = true;
break;
case CallState.StreamsRunning:
@@ -152,7 +151,7 @@ namespace _04_BasicChat.Views
case CallState.End:
case CallState.Released:
IncomingCall = null;
IncommingCall = null;
EndingCallGuiUpdates();
VideoService.StopVideoStream();
break;
@@ -179,7 +178,7 @@ namespace _04_BasicChat.Views
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
CallButton.IsEnabled = true;
HangUp.IsEnabled = false;
HangOut.IsEnabled = false;
Sound.IsEnabled = false;
Camera.IsEnabled = false;
Mic.IsEnabled = false;
@@ -193,7 +192,7 @@ namespace _04_BasicChat.Views
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
CallButton.IsEnabled = false;
HangUp.IsEnabled = true;
HangOut.IsEnabled = true;
Sound.IsEnabled = true;
Camera.IsEnabled = true;
Mic.IsEnabled = true;

View File

@@ -40,7 +40,7 @@ namespace _04_BasicChat.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
ChatRoom = (ChatRoom)e.Parameter;
ChatRoom = ((ChatRoom)e.Parameter);
// The ChatRoom also offers to register to some callbacks.
// One of them is OnMessageReceived, like the one we used
@@ -49,17 +49,17 @@ namespace _04_BasicChat.Views
// ChatRoom.
ChatRoom.Listener.OnMessageReceived += OnMessageReceived;
// The method GetHistory gets all the ChatMessage's you have
// The method GetHistory get all the ChatMessage you have
// in your local database for this ChatRoom. GetHistory(0)
// means 'all of them', but you can specify a max number of messages.
// means everything but you can specify a max number of messages.
foreach (ChatMessage chatMessage in ChatRoom.GetHistory(0))
{
// See AddMessage(ChatMessage chatMessage) to see how we display messages
AddMessage(chatMessage);
}
// Mark all the messages in the ChatRoom as read, if some messages
// weren't, this will send read notifications to the remote.
// Mark all the messages in th ChatRoom as read, if some messages
// weren't, this will trigger some read notifications to the remote.
ChatRoom.MarkAsRead();
// Only here to update display of unread message count on parent frames.
@@ -99,8 +99,8 @@ namespace _04_BasicChat.Views
TextBlock textBlock = new TextBlock();
// You can find a lot of information on a ChatMessage object.
// Here we use the IsOutgoing info to choose which side
// of the frame the message should be displayed on.
// Here we used the IsOutgoing info to choose on each side
// of the frame the message should be displayed.
if (chatMessage.IsOutgoing)
{
textBlock.HorizontalAlignment = HorizontalAlignment.Right;
@@ -110,13 +110,13 @@ namespace _04_BasicChat.Views
textBlock.HorizontalAlignment = HorizontalAlignment.Left;
}
// We take the first element of the Contents list of our ChatMessage. To keep
// You can see we take the first element of the Contents list of our ChatMessage. To keep
// it simple we assume that we only send simple text message, we will talk more about multipart
// messages and other types of messagse in the next step.
// For now we only handle chat messages with a single content item, so we can find our text in
// For now we only handle chat messages with one content, so we can find our text in
// chatMessage.Contents.First().Utf8Text.
// We wrap in a dollar string because if the message is e.g. a file transfer, the Utf8Text can be null.
textBlock.Text = $"{chatMessage.Contents.First().Utf8Text}";
// We used ["" +] because if the message is a file transfer for example the Utf8Text can be null.
textBlock.Text = "" + chatMessage.Contents.First().Utf8Text;
MessagesList.Children.Add(textBlock);
@@ -136,7 +136,8 @@ namespace _04_BasicChat.Views
{
if (ChatRoom != null && OutgoingMessageText.Text != null && OutgoingMessageText.Text.Length > 0)
{
// We use the ChatRoom to create a new ChatMessage object.
// We use the ChatRoom to create a new ChatMessage object. Here we used
// the method CreateMessage(string message) to create a text message.
ChatMessage chatMessage = ChatRoom.CreateMessage(OutgoingMessageText.Text);
// And simply call the Send() method to send the message.

View File

@@ -51,8 +51,8 @@ namespace _04_BasicChat.Views
// Here we want to update the list every time a message is
// received (list order and unread message count, see ChatsPage.xaml)
// or sent (list order)
CoreService.AddOnOnMessageReceivedDelegate(OnMessageReceivedOrSent);
CoreService.AddOnMessageSentDelegate(OnMessageReceivedOrSent);
CoreService.AddOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
CoreService.AddOnMessageSentDelegate(OnMessageReceiveOrSent);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
@@ -61,16 +61,16 @@ namespace _04_BasicChat.Views
// You need to unregister delegate to allow the garbage collector to
// collect this instance when you navigate away.
CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReceivedOrSent);
CoreService.RemoveOnMessageSentDelegate(OnMessageReceivedOrSent);
CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReceiveOrSent);
CoreService.RemoveOnMessageSentDelegate(OnMessageReceiveOrSent);
base.OnNavigatedFrom(e);
}
/// <summary>
/// Method called to update the list every time a message is received or sent.
/// Method called too update the list every time a message is received or sent.
/// </summary>
private void OnMessageReceivedOrSent(Core core, ChatRoom chatRoom, ChatMessage message) => UpdateChatRooms();
private void OnMessageReceiveOrSent(Core core, ChatRoom chatRoom, ChatMessage message) => UpdateChatRooms();
public void UpdateChatRooms()
{
@@ -80,13 +80,13 @@ namespace _04_BasicChat.Views
// In the ChatRooms list attribute you can find every ChatRooms linked
// to your user. The list is ordered by ChatRoom last activity date
// (most recent first).
// You can see in ChatsPage.xaml that we only use the properties
// You can see in Chats.xaml that we only use the properties
// UnreadMessagesCount and PeerAdress to display our chat rooms.
// In later steps we will do more.
// In further steps we will do more.
foreach (ChatRoom chatRoom in CoreService.Core.ChatRooms)
{
// Here we use the HistorySize attribute to display only
// ChatRooms where at least one message was exchanged.
// ChatRooms were at least one message was exchange.
if (chatRoom.HistorySize > 0)
{
ChatRoomsLV.Items.Add(chatRoom);
@@ -110,33 +110,31 @@ namespace _04_BasicChat.Views
private async void NewChatRoom_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
string peerSipAddress = await InputTextDialogAsync("Enter peer sip address");
if (string.IsNullOrWhiteSpace(peerSipAddress))
if (!String.IsNullOrWhiteSpace(peerSipAddress))
{
return;
}
// We create a new ChatRoom with the address the user gave us.
// See CoreService.CreateOrGetChatRoom(string sipAddress) for more info
ChatRoom newChatRoom = CoreService.CreateOrGetChatRoom(peerSipAddress);
// We create a new ChatRoom with the address the user gave us.
// See CoreService.CreateOrGetChatRoom(string sipAddress) for more info
ChatRoom newChatRoom = CoreService.CreateOrGetChatRoom(peerSipAddress);
if (newChatRoom != null)
{
// If the ChatRoom creation succeeded, render/navigate to a ChatPage in the inner
// frame of the ChatsPage.
// See ChatPage.xaml.cs to understand how to get message history and how to send/receive
// and display new messages.
ChatRoomFrame.Navigate(typeof(ChatPage), newChatRoom);
}
else
{
ContentDialog chatRoomCreationErrDialog = new ContentDialog
if (newChatRoom != null)
{
Title = "ChatRoom creation error",
Content = "An error occurred during ChatRoom creation, check sip address validity and try again.",
CloseButtonText = "OK"
};
// If the ChatRoom creation succeed render/navigate to a ChatPage in the inner
// frame of the ChatsPage.
// See ChatPage.xaml.cs to understand how to get message history and how to send/receive
// and display new messages.
ChatRoomFrame.Navigate(typeof(ChatPage), newChatRoom);
}
else
{
ContentDialog noSettingsDialog = new ContentDialog
{
Title = "ChatRoom creation error",
Content = "An error occurred during ChatRoom creation, check sip address validity and try again.",
CloseButtonText = "OK"
};
await chatRoomCreationErrDialog.ShowAsync();
await noSettingsDialog.ShowAsync();
}
}
}

View File

@@ -28,7 +28,7 @@ using Windows.UI.Xaml.Navigation;
namespace _04_BasicChat.Views
{
/// <summary>
/// Introduced in step 02 IncomingCall
/// A really simple app for a first Login with LinphoneSDK x UWP
/// </summary>
public sealed partial class NavigationRoot : Page
{
@@ -44,23 +44,26 @@ namespace _04_BasicChat.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this.CoreService.AddOnOnMessageReceivedDelegate(OnMessageReceived);
this.CoreService.AddOnOnMessageReceivedDelegate(OnMessageReveive);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
this.CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReceived);
this.CoreService.RemoveOnOnMessageReceivedDelegate(OnMessageReveive);
base.OnNavigatedFrom(e);
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Only do an inital navigate the first time the page loads
// when we switch out of compactoverloadmode this will fire but we don't want to navigate because
// there is already a page loaded
if (!hasLoadedPreviously)
{
AppNavFrame.Navigate(typeof(CallsPage));
UpdateUnreadMessageCount();
hasLoadedPreviously = true;
NavigationService.CurrentNavigationRoot = this; // NEW!
NavigationService.CurrentNavigationRoot = this;
}
}
@@ -68,12 +71,11 @@ namespace _04_BasicChat.Views
{
switch (e.SourcePageType)
{
case Type _ when e.SourcePageType == typeof(CallsPage):
case Type c when e.SourcePageType == typeof(CallsPage):
((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
break;
// NEW!
case Type _ when e.SourcePageType == typeof(ChatsPage):
case Type c when e.SourcePageType == typeof(ChatsPage):
((NavigationViewItem)navview.MenuItems[1]).IsSelected = true;
break;
}
@@ -86,20 +88,22 @@ namespace _04_BasicChat.Views
ContentDialog noSettingsDialog = new ContentDialog
{
Title = "No settings",
Content = "There are no settings in this little app",
Content = "There is no settings in this little app",
CloseButtonText = "OK"
};
_ = await noSettingsDialog.ShowAsync();
ContentDialogResult result = await noSettingsDialog.ShowAsync();
return;
}
if (args.InvokedItem is string invokedItemValue && invokedItemValue.Contains("Calls"))
string invokedItemValue = args.InvokedItem as string;
if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
{
_ = AppNavFrame.Navigate(typeof(CallsPage));
AppNavFrame.Navigate(typeof(CallsPage));
}
else // NEW!
else
{
_ = AppNavFrame.Navigate(typeof(ChatsPage));
AppNavFrame.Navigate(typeof(ChatsPage));
}
}
@@ -113,7 +117,7 @@ namespace _04_BasicChat.Views
ContentDialog signOutDialog = new ContentDialog
{
Title = "Sign out ?",
Content = "All your current calls and actions will be canceled.",
Content = "All your current calls and actions will be canceled, are you sure to continue ?",
PrimaryButtonText = "Sign out",
CloseButtonText = "Cancel"
};
@@ -129,22 +133,20 @@ namespace _04_BasicChat.Views
}
}
// NEW!
private void OnMessageReceived(Core core, ChatRoom chatRoom, ChatMessage message)
private void OnMessageReveive(Core core, ChatRoom chatRoom, ChatMessage message)
{
UpdateUnreadMessageCount();
}
// NEW!
public void UpdateUnreadMessageCount()
{
// The property UnreadChatMessageCountFromActiveLocals gives the total
// number of unread messages in all the chat rooms of all the connected accounts
// The property UnreadChatMessageCountFromActiveLocals return the total
// number of unread messages in all the chat rooms off all connected accounts
// on the device. In the tutorial we only allow one account at a time, so
// you get the global unread message count for your account.
if (CoreService.Core.UnreadChatMessageCountFromActiveLocals > 0)
{
NewMessageCount.Text = CoreService.Core.UnreadChatMessageCountFromActiveLocals.ToString();
NewMessageCount.Text = "" + CoreService.Core.UnreadChatMessageCountFromActiveLocals;
NewMessageCountBorder.Visibility = Visibility.Visible;
}
else

View File

@@ -192,10 +192,10 @@
<Version>2.1.13</Version>
</PackageReference>
<PackageReference Include="LinphoneSDK">
<Version>5.1.0</Version>
<Version>5.1.0-alpha.56</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
<Version>6.2.11</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -26,7 +26,7 @@
<TextBlock x:Name="FileName" Text="" FontWeight="Bold" />
<TextBlock x:Name="FileSize" Text="" />
<Button x:Name="Download" Content="Download" Click="Download_Click" Visibility="Collapsed" />
<Button x:Name="OpenFolder" Content="Open folder" Click="OpenFolder_Click" Visibility="Collapsed" />
<Button x:Name="OpenFile" Content="Open file" Click="OpenFile_Click" Visibility="Collapsed" />
</StackPanel>
<TextBlock x:Name="MessageState" />

View File

@@ -58,13 +58,13 @@ namespace _05_FileTransfer.Controls
private void OnMessageStateChanged(ChatMessage message, ChatMessageState state)
{
// We display the message state. It can be really useful for the user
// to know if the remote only received the message (state = Delivered) or
// if they read it (state = Displayed)
// to know if the remote received the message (state = Delivered) or
// if he read it (state = Displayed)
MessageState.Text = "The message state is : " + state;
switch (state)
{
// A file transfer can be in multiple states (FileTransferInProgress,
// They're is multiple state during a file transfer (FileTransferInProgress,
// FileTransferDone, FileTransferError). We update the layout if the file
// is done downloading to replace the "Download" button by an "Open file" button.
case ChatMessageState.FileTransferDone:
@@ -78,7 +78,7 @@ namespace _05_FileTransfer.Controls
MessageState.Text = "The message state is : " + ChatMessage.State;
// You can find the sending date of a ChatMessage in ChatMessage.Time.
// The time number follows the time_t type specification. (Unix timestamp)
// The time number respect the time_t type specification.
ReceiveDate.Text = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(ChatMessage.Time).ToLocalTime().ToString("HH:mm");
if (ChatMessage.IsOutgoing)
@@ -90,11 +90,11 @@ namespace _05_FileTransfer.Controls
this.HorizontalAlignment = HorizontalAlignment.Left;
}
// A ChatMessage holds a list of Content objects, Contents.
// A ChatMessage hold a list of Content object, Contents.
// But in a basic ChatRoom, using a basic backend, by default the multipart
// is disabled. So in any received message there is only one Content in the list.
// is disable. So in any received message there is only one Content in the list.
// You can enable multipart on a ChatRoom object with ChatRoom.AllowMultipart() but it
// can be risky. In fact if your remote doesn't support multipart and you send them
// can be risky. In fact if your remote doesn't support multipart and you send him
// a multipart message it could not work properly.
if (ChatMessage.Contents.Any(c => c.IsFile))
{
@@ -104,7 +104,7 @@ namespace _05_FileTransfer.Controls
// the Content object.
TextStack.Visibility = Visibility.Collapsed;
FileStack.Visibility = Visibility.Visible;
OpenFolder.Visibility = Visibility.Visible;
OpenFile.Visibility = Visibility.Visible;
Download.Visibility = Visibility.Collapsed;
// We can do this because we don't allowMultipart and can assume
@@ -126,7 +126,7 @@ namespace _05_FileTransfer.Controls
TextStack.Visibility = Visibility.Collapsed;
FileStack.Visibility = Visibility.Visible;
Download.Visibility = Visibility.Visible;
OpenFolder.Visibility = Visibility.Collapsed;
OpenFile.Visibility = Visibility.Collapsed;
Content content = ChatMessage.Contents.First((c) => c.IsFileTransfer);
@@ -173,13 +173,16 @@ namespace _05_FileTransfer.Controls
}
}
private async void OpenFolder_Click(object sender, RoutedEventArgs e)
private async void OpenFile_Click(object sender, RoutedEventArgs e)
{
// Linphone can sometimes return paths with Unix-style forward slashes ('/').
// System.IO.Path.* methods are made to deal with such paths.
var folderPath = Path.GetDirectoryName(CurrentShownContent.FilePath);
var folder = await StorageFolder.GetFolderFromPathAsync(folderPath);
_ = await Launcher.LaunchFolderAsync(folder);
// Just get the FilePath attribute from the Content object
string filePath = CurrentShownContent.FilePath;
// Only keep the folder part
string folderPath = filePath.Substring(0, filePath.LastIndexOf("\\"));
// And launch the Windows explorer
await Launcher.LaunchFolderAsync(await StorageFolder.GetFolderFromPathAsync(folderPath));
}
}
}

View File

@@ -3,11 +3,15 @@
Learn how to send files over SIP using Linphone SDK.
We will add a button to send files to your peer, and improve the display of messages to
include more metadata and allow the user to download files sent by the remote end.
Most of the new Linphone uses are in Controls/MessageDisplay.xaml(.cs) and ChatPage.xaml(.cs) but don't
We added a button to send file to your peer, and we improved how messages are displayed to show
you more information about them and allow you to download files sent by the remote end.
Most of the new Linphone usage are in Controls/MessageDisplay.xaml(.cs) and ChatPage.xaml(.cs) but don't
forget to set the attribute FileTransferServer on your Core ! (see Core creation in CoreService.cs)
Don't forget to install those NuGet packages :
- LinphoneSDK (can be found here : https://www.linphone.org/snapshots/windows/sdk/)
- Microsoft.NETCore.UniversalWindowsPlatform (version 6.2.12 recommended)
- ANGLE.WindowsStore (for video rendering, version 2.1.13 recommended)
New/updated files :
@@ -15,7 +19,7 @@ New/updated files :
05_FileTransfer
└───Controls :
│ │ MessageDisplay.xaml(.cs) : A user control to display chat bubbles with more
│ │ information. Learn how to handle the different types of ChatMessage's here.
│ │ information. Learn how to handle the different types of ChatMessage here.
│ │
└───Service :
@@ -25,5 +29,5 @@ New/updated files :
└───Views :
│ │
│ │ ChatPage.xaml(.cs) : This is the frame displayed when you select a chat room.
│ │ You can now send files and message display is improved (see MessageDisplay)
│ │ You can now send file and the message display is improved (see MessageDisplay)
```

View File

@@ -75,8 +75,10 @@ namespace _05_FileTransfer.Service
videoActivationPolicy.AutomaticallyInitiate = false;
core.VideoActivationPolicy = videoActivationPolicy;
core.VideoCaptureEnabled = core.VideoSupported();
if (core.VideoSupported())
{
core.VideoCaptureEnabled = true;
}
core.UsePreviewWindow(true);
// You must set up your file transfer server if you want to transfer files.
@@ -184,17 +186,17 @@ namespace _05_FileTransfer.Service
Core.InviteAddress(address);
}
public bool ToggleMic()
public bool MicEnabledSwitch()
{
return Core.MicEnabled = !Core.MicEnabled;
}
public bool ToggleSpeaker()
public bool SpeakerMutedSwitch()
{
return Core.CurrentCall.SpeakerMuted = !Core.CurrentCall.SpeakerMuted;
}
public async Task<bool> ToggleCameraAsync()
public async Task<bool> CameraEnabledSwitchAsync()
{
await OpenCameraPopup();
@@ -233,22 +235,21 @@ namespace _05_FileTransfer.Service
// File Path is the only mandatory field to set.
content.FilePath = fileCopy.Path;
// You can set the type and subtype of your file, it helps
// the server and receiver to identify the file (images can
// You can set the type and subtype of your file, it help
// the server and receiver identifying the file (images can
// be directly displayed for example).
string[] splitMimeType = fileCopy.ContentType.Split("/");
content.Type = splitMimeType[0];
content.Subtype = splitMimeType[1];
string[] splittedMimeType = fileCopy.ContentType.Split("/");
content.Type = splittedMimeType[0];
content.Subtype = splittedMimeType[1];
// You can set the file name on the receiver's end. For the purposes of
// demonstration we set it to the original's name, but this is unnecessary
// since this is the default behaviour.
// Set the file name for the receiver, by default the same name is taken.
// This line is useful only for the explanation.
content.Name = fileCopy.Name;
return content;
}
public async Task OpenMicrophonePopup()
private async Task OpenMicrophonePopup()
{
AudioGraphSettings settings = new AudioGraphSettings(Windows.Media.Render.AudioRenderCategory.Media);
CreateAudioGraphResult result = await AudioGraph.CreateAsync(settings);
@@ -263,18 +264,11 @@ namespace _05_FileTransfer.Service
private async Task OpenCameraPopup()
{
MediaCapture mediaCapture = new MediaCapture();
try
MediaCapture mediaCapture = new Windows.Media.Capture.MediaCapture();
await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
{
await mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings
{
StreamingCaptureMode = StreamingCaptureMode.Video
});
}
catch (Exception e) when (e.Message.StartsWith("No capture devices are available."))
{
// Ignored.
}
StreamingCaptureMode = StreamingCaptureMode.Video
});
mediaCapture.Dispose();
}
}

View File

@@ -32,7 +32,7 @@
<TextBlock x:Name="CallText" Text="Your call state is : Idle" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10">
<Button x:Name="HangUp" Content="Hang up" Click="OnHangUpClicked" IsEnabled="False" />
<Button x:Name="HangOut" Content="Hang out" Click="HangOutClick" IsEnabled="False" />
<Button x:Name="Sound" Content="Switch off Sound" Click="SoundClick" IsEnabled="False" />
<Button x:Name="Camera" Content="Switch on Camera" Click="CameraClick" IsEnabled="False" />
<Button x:Name="Mic" Content="Mute" Click="MicClick" IsEnabled="False" />

View File

@@ -31,7 +31,7 @@ namespace _05_FileTransfer.Views
private VideoService VideoService { get; } = VideoService.Instance;
private Call IncomingCall;
private Call IncommingCall;
public CallsPage()
{
@@ -63,14 +63,14 @@ namespace _05_FileTransfer.Views
CoreService.Call(UriToCall.Text);
}
private void OnHangUpClicked(object sender, RoutedEventArgs e)
private void HangOutClick(object sender, RoutedEventArgs e)
{
CoreService.Core.TerminateAllCalls();
}
private void SoundClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleSpeaker())
if (CoreService.SpeakerMutedSwitch())
{
Sound.Content = "Switch on Sound";
}
@@ -82,14 +82,14 @@ namespace _05_FileTransfer.Views
private async void CameraClick(object sender, RoutedEventArgs e)
{
await CoreService.ToggleCameraAsync();
await CoreService.CameraEnabledSwitchAsync();
Camera.Content = "Waiting for accept ...";
Camera.IsEnabled = false;
}
private void MicClick(object sender, RoutedEventArgs e)
{
if (CoreService.ToggleMic())
if (CoreService.MicEnabledSwitch())
{
Mic.Content = "Mute";
}
@@ -99,19 +99,18 @@ namespace _05_FileTransfer.Views
}
}
private async void AnswerClick(object sender, RoutedEventArgs e)
private void AnswerClick(object sender, RoutedEventArgs e)
{
await CoreService.OpenMicrophonePopup();
IncomingCall.Accept();
IncomingCall = null;
IncommingCall.Accept();
IncommingCall = null;
}
private void DeclineClick(object sender, RoutedEventArgs e)
{
if (IncomingCall != null)
if (IncommingCall != null)
{
IncomingCall.Decline(Reason.Declined);
IncomingCall = null;
IncommingCall.Decline(Reason.Declined);
IncommingCall = null;
}
}
@@ -122,7 +121,7 @@ namespace _05_FileTransfer.Views
{
case CallState.IncomingReceived:
IncomingCall = call;
IncommingCall = call;
IncomingCallStackPanel.Visibility = Visibility.Visible;
IncommingCallText.Text = " " + call.RemoteAddress.AsString();
break;
@@ -131,7 +130,7 @@ namespace _05_FileTransfer.Views
case CallState.OutgoingProgress:
case CallState.OutgoingRinging:
HangUp.IsEnabled = true;
HangOut.IsEnabled = true;
break;
case CallState.StreamsRunning:
@@ -152,7 +151,7 @@ namespace _05_FileTransfer.Views
case CallState.End:
case CallState.Released:
IncomingCall = null;
IncommingCall = null;
EndingCallGuiUpdates();
VideoService.StopVideoStream();
break;
@@ -179,7 +178,7 @@ namespace _05_FileTransfer.Views
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
CallButton.IsEnabled = true;
HangUp.IsEnabled = false;
HangOut.IsEnabled = false;
Sound.IsEnabled = false;
Camera.IsEnabled = false;
Mic.IsEnabled = false;
@@ -193,7 +192,7 @@ namespace _05_FileTransfer.Views
{
IncomingCallStackPanel.Visibility = Visibility.Collapsed;
CallButton.IsEnabled = false;
HangUp.IsEnabled = true;
HangOut.IsEnabled = true;
Sound.IsEnabled = true;
Camera.IsEnabled = true;
Mic.IsEnabled = true;

View File

@@ -44,7 +44,7 @@ namespace _05_FileTransfer.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
ChatRoom = (ChatRoom)e.Parameter;
ChatRoom = ((ChatRoom)e.Parameter);
ChatHeaderText.Text += ChatRoom.PeerAddress.Username;
ChatRoom.Listener.OnMessageReceived += OnMessageReceived;
foreach (ChatMessage chatMessage in ChatRoom.GetHistory(0))
@@ -76,7 +76,7 @@ namespace _05_FileTransfer.Views
private void AddMessage(ChatMessage chatMessage)
{
// Instead of simply displaying a TextBlock we now create a
// Instead of simply display a TextBlock we now create a
// MessageDisplay object to show more informations about the message.
// See Controls/MessageDisplay.xaml(.cs)
MessageDisplay messageDisplay = new MessageDisplay(chatMessage);
@@ -110,11 +110,9 @@ namespace _05_FileTransfer.Views
{
// Basic Windows code to let the user select a file and gain
// read access to a StorageFile object.
FileOpenPicker picker = new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
FileOpenPicker picker = new FileOpenPicker();
picker.ViewMode = PickerViewMode.List;
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.FileTypeFilter.Add("*");
StorageFile file = await picker.PickSingleFileAsync();

View File

@@ -28,8 +28,7 @@ using Windows.UI.Xaml.Navigation;
namespace _05_FileTransfer.Views
{
/// <summary>
/// Introduced in step 02 IncomingCall
/// Changed in step 04 BasicChat
/// A really simple app for a first Login with LinphoneSDK x UWP
/// </summary>
public sealed partial class NavigationRoot : Page
{
@@ -56,6 +55,9 @@ namespace _05_FileTransfer.Views
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Only do an inital navigate the first time the page loads
// when we switch out of compactoverloadmode this will fire but we don't want to navigate because
// there is already a page loaded
if (!hasLoadedPreviously)
{
AppNavFrame.Navigate(typeof(CallsPage));
@@ -69,11 +71,11 @@ namespace _05_FileTransfer.Views
{
switch (e.SourcePageType)
{
case Type _ when e.SourcePageType == typeof(CallsPage):
case Type c when e.SourcePageType == typeof(CallsPage):
((NavigationViewItem)navview.MenuItems[0]).IsSelected = true;
break;
case Type _ when e.SourcePageType == typeof(ChatsPage):
case Type c when e.SourcePageType == typeof(ChatsPage):
((NavigationViewItem)navview.MenuItems[1]).IsSelected = true;
break;
}
@@ -86,21 +88,22 @@ namespace _05_FileTransfer.Views
ContentDialog noSettingsDialog = new ContentDialog
{
Title = "No settings",
Content = "There are no settings in this little app",
Content = "There is no settings in this little app",
CloseButtonText = "OK"
};
_ = await noSettingsDialog.ShowAsync();
ContentDialogResult result = await noSettingsDialog.ShowAsync();
return;
}
string invokedItemValue = args.InvokedItem as string;
if (invokedItemValue != null && invokedItemValue.Contains("Calls"))
{
_ = AppNavFrame.Navigate(typeof(CallsPage));
AppNavFrame.Navigate(typeof(CallsPage));
}
else
{
_ = AppNavFrame.Navigate(typeof(ChatsPage));
AppNavFrame.Navigate(typeof(ChatsPage));
}
}
@@ -114,7 +117,7 @@ namespace _05_FileTransfer.Views
ContentDialog signOutDialog = new ContentDialog
{
Title = "Sign out ?",
Content = "All your current calls and actions will be canceled.",
Content = "All your current calls and actions will be canceled, are you sure to continue ?",
PrimaryButtonText = "Sign out",
CloseButtonText = "Cancel"
};

View File

@@ -225,10 +225,10 @@
<Version>2.1.13</Version>
</PackageReference>
<PackageReference Include="LinphoneSDK">
<Version>5.1.0</Version>
<Version>5.1.0-alpha.56</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.13</Version>
<Version>6.2.11</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -14,7 +14,7 @@
<TextBlock x:Name="FileName" Text="" FontWeight="Bold" />
<TextBlock x:Name="FileSize" Text="" />
<Button x:Name="Download" Content="Download" Click="Download_Click" Visibility="Collapsed" />
<Button x:Name="OpenFolder" Content="Open folder" Click="OpenFolder_Click" Visibility="Collapsed" />
<Button x:Name="OpenFile" Content="Open file" Click="OpenFile_Click" Visibility="Collapsed" />
</StackPanel>
</Grid>
</UserControl>

Some files were not shown because too many files have changed in this diff Show More