@flashphoner/websdk 2.0.208 → 2.0.212
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docTemplate/README.md +1 -1
- package/examples/demo/streaming/2players/2players.js +0 -5
- package/examples/demo/streaming/canvas_streaming/canvas_streaming.js +0 -12
- package/examples/demo/streaming/conference/conference.html +6 -0
- package/examples/demo/streaming/conference/conference.js +83 -23
- package/examples/demo/streaming/embed_player/player.js +127 -198
- package/examples/demo/streaming/firewall-traversal-streaming/firewall-traversal-streaming.js +0 -12
- package/examples/demo/streaming/mcu_client/mcu_client.js +0 -8
- package/examples/demo/streaming/media_devices_manager/manager.js +0 -12
- package/examples/demo/streaming/player/player.js +10 -9
- package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.html +77 -1
- package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.js +472 -84
- package/examples/demo/streaming/stream-diagnostic/stream-diagnostic.js +0 -8
- package/examples/demo/streaming/stream-local-snapshot/stream-local-snapshot.js +0 -6
- package/examples/demo/streaming/stream-snapshot/stream-snapshot.js +0 -6
- package/examples/demo/streaming/stream_recording/recording.js +0 -6
- package/examples/demo/streaming/streamer/streamer.js +0 -8
- package/examples/demo/streaming/two_way_streaming/two_way_streaming.js +0 -11
- package/examples/demo/streaming/webrtc-as-rtmp-republishing/webrtc-as-rtmp-republishing.js +0 -6
- package/flashphoner-no-flash.js +118 -14
- package/flashphoner-no-flash.min.js +2 -2
- package/flashphoner-no-webrtc.js +89 -5
- package/flashphoner-no-webrtc.min.js +1 -1
- package/flashphoner-no-wsplayer.js +118 -14
- package/flashphoner-no-wsplayer.min.js +2 -2
- package/flashphoner-room-api.js +104 -9
- package/flashphoner-room-api.min.js +2 -2
- package/flashphoner-temasys-flash-websocket-without-adapterjs.js +89 -5
- package/flashphoner-temasys-flash-websocket.js +89 -5
- package/flashphoner-temasys-flash-websocket.min.js +1 -1
- package/flashphoner-webrtc-only.js +118 -14
- package/flashphoner-webrtc-only.min.js +1 -1
- package/flashphoner.js +118 -14
- package/flashphoner.min.js +2 -2
- package/package.json +1 -1
- package/src/flashphoner-core.d.ts +22 -5
- package/src/flashphoner-core.js +79 -3
- package/src/webrtc-media-provider.js +25 -6
|
@@ -12656,6 +12656,9 @@ var getSession = function getSession(id) {
|
|
|
12656
12656
|
* @param {Object=} options.sipOptions Sip configuration
|
|
12657
12657
|
* @param {Object=} options.mediaOptions Media connection configuration
|
|
12658
12658
|
* @param {Integer=} options.timeout Connection timeout in milliseconds
|
|
12659
|
+
* @param {Integer=} options.pingInterval Server ping interval in milliseconds [0]
|
|
12660
|
+
* @param {Integer=} options.receiveProbes A maximum subsequental pings received missing count [0]
|
|
12661
|
+
* @param {Integer=} options.probesInterval Interval to check subsequental pings received [0]
|
|
12659
12662
|
* @returns {Session} Created session
|
|
12660
12663
|
* @throws {Error} Error if API is not initialized
|
|
12661
12664
|
* @throws {TypeError} Error if options.urlServer is not specified
|
|
@@ -12682,6 +12685,8 @@ var createSession = function createSession(options) {
|
|
|
12682
12685
|
var mediaOptions = options.mediaOptions;
|
|
12683
12686
|
var keepAlive = options.keepAlive;
|
|
12684
12687
|
var timeout = options.timeout;
|
|
12688
|
+
var wsPingSender = new WSPingSender(options.pingInterval || 0);
|
|
12689
|
+
var wsPingReceiver = new WSPingReceiver(options.receiveProbes || 0, options.probesInterval || 0);
|
|
12685
12690
|
var connectionTimeout;
|
|
12686
12691
|
var cConfig; //SIP config
|
|
12687
12692
|
|
|
@@ -12799,7 +12804,7 @@ var createSession = function createSession(options) {
|
|
|
12799
12804
|
mediaProviders: Object.keys(MediaProvider),
|
|
12800
12805
|
keepAlive: keepAlive,
|
|
12801
12806
|
authToken: authToken,
|
|
12802
|
-
clientVersion: "2.0.
|
|
12807
|
+
clientVersion: "2.0.212",
|
|
12803
12808
|
clientOSVersion: window.navigator.appVersion,
|
|
12804
12809
|
clientBrowserVersion: window.navigator.userAgent,
|
|
12805
12810
|
msePacketizationVersion: 2,
|
|
@@ -12812,7 +12817,11 @@ var createSession = function createSession(options) {
|
|
|
12812
12817
|
|
|
12813
12818
|
|
|
12814
12819
|
send("connection", cConfig);
|
|
12815
|
-
logger.setConnection(wsConnection);
|
|
12820
|
+
logger.setConnection(wsConnection); // Send ping messages to server to check if connection is still alive #WCS-3410
|
|
12821
|
+
|
|
12822
|
+
wsPingSender.start(); // Check subsequintel pings received from server to check if connection is still alive #WCS-3410
|
|
12823
|
+
|
|
12824
|
+
wsPingReceiver.start();
|
|
12816
12825
|
};
|
|
12817
12826
|
|
|
12818
12827
|
wsConnection.onmessage = function (event) {
|
|
@@ -12828,6 +12837,7 @@ var createSession = function createSession(options) {
|
|
|
12828
12837
|
switch (data.message) {
|
|
12829
12838
|
case 'ping':
|
|
12830
12839
|
send("pong", null);
|
|
12840
|
+
wsPingReceiver.success();
|
|
12831
12841
|
break;
|
|
12832
12842
|
|
|
12833
12843
|
case 'getUserData':
|
|
@@ -12987,7 +12997,11 @@ var createSession = function createSession(options) {
|
|
|
12987
12997
|
sessionStatus = newStatus;
|
|
12988
12998
|
|
|
12989
12999
|
if (sessionStatus == SESSION_STATUS.DISCONNECTED || sessionStatus == SESSION_STATUS.FAILED) {
|
|
12990
|
-
//
|
|
13000
|
+
// Stop pinging server #WCS-3410
|
|
13001
|
+
wsPingSender.stop(); // Stop checking pings received #WCS-3410
|
|
13002
|
+
|
|
13003
|
+
wsPingReceiver.stop(); //remove streams
|
|
13004
|
+
|
|
12991
13005
|
for (var prop in streamRefreshHandlers) {
|
|
12992
13006
|
if (streamRefreshHandlers.hasOwnProperty(prop) && typeof streamRefreshHandlers[prop] === 'function') {
|
|
12993
13007
|
streamRefreshHandlers[prop]({
|
|
@@ -13003,6 +13017,73 @@ var createSession = function createSession(options) {
|
|
|
13003
13017
|
if (callbacks[sessionStatus]) {
|
|
13004
13018
|
callbacks[sessionStatus](session, obj);
|
|
13005
13019
|
}
|
|
13020
|
+
} // Websocket periodic ping sender
|
|
13021
|
+
|
|
13022
|
+
|
|
13023
|
+
function WSPingSender(interval) {
|
|
13024
|
+
this.interval = interval || 0;
|
|
13025
|
+
this.intervalId = null;
|
|
13026
|
+
|
|
13027
|
+
this.start = function () {
|
|
13028
|
+
if (this.interval > 0) {
|
|
13029
|
+
this.intervalId = setInterval(function () {
|
|
13030
|
+
send("ping", null);
|
|
13031
|
+
}, this.interval);
|
|
13032
|
+
}
|
|
13033
|
+
};
|
|
13034
|
+
|
|
13035
|
+
this.stop = function () {
|
|
13036
|
+
if (this.intervalId) {
|
|
13037
|
+
clearInterval(this.intervalId);
|
|
13038
|
+
}
|
|
13039
|
+
};
|
|
13040
|
+
|
|
13041
|
+
return this;
|
|
13042
|
+
} // Websocket ping receive prober
|
|
13043
|
+
|
|
13044
|
+
|
|
13045
|
+
function WSPingReceiver(receiveProbes, probesInterval) {
|
|
13046
|
+
this.maxPings = receiveProbes || 0;
|
|
13047
|
+
this.interval = probesInterval || 0;
|
|
13048
|
+
this.intervalId = null;
|
|
13049
|
+
this.pingsMissing = 0;
|
|
13050
|
+
|
|
13051
|
+
this.start = function () {
|
|
13052
|
+
if (this.maxPings > 0 && this.interval > 0) {
|
|
13053
|
+
var receiver = this;
|
|
13054
|
+
this.intervalId = setInterval(function () {
|
|
13055
|
+
receiver.checkPingsReceived();
|
|
13056
|
+
}, this.interval);
|
|
13057
|
+
}
|
|
13058
|
+
};
|
|
13059
|
+
|
|
13060
|
+
this.stop = function () {
|
|
13061
|
+
if (this.intervalId) {
|
|
13062
|
+
clearInterval(this.intervalId);
|
|
13063
|
+
}
|
|
13064
|
+
|
|
13065
|
+
this.pingsMissing = 0;
|
|
13066
|
+
};
|
|
13067
|
+
|
|
13068
|
+
this.checkPingsReceived = function () {
|
|
13069
|
+
this.pingsMissing++;
|
|
13070
|
+
|
|
13071
|
+
if (this.pingsMissing >= this.maxPings) {
|
|
13072
|
+
this.failure();
|
|
13073
|
+
}
|
|
13074
|
+
};
|
|
13075
|
+
|
|
13076
|
+
this.success = function () {
|
|
13077
|
+
this.pingsMissing = 0;
|
|
13078
|
+
};
|
|
13079
|
+
|
|
13080
|
+
this.failure = function () {
|
|
13081
|
+
logger.info(LOG_PREFIX, "Missing " + this.pingsMissing + " pings from server, connection seems to be down");
|
|
13082
|
+
onSessionStatusChange(SESSION_STATUS.FAILED);
|
|
13083
|
+
wsConnection.close();
|
|
13084
|
+
};
|
|
13085
|
+
|
|
13086
|
+
return this;
|
|
13006
13087
|
}
|
|
13007
13088
|
/**
|
|
13008
13089
|
* @callback sdpHook
|
|
@@ -13867,7 +13948,7 @@ var createSession = function createSession(options) {
|
|
|
13867
13948
|
* @param {HTMLElement} options.display Div element stream should be displayed in
|
|
13868
13949
|
* @param {Object=} options.custom User provided custom object that will be available in REST App code
|
|
13869
13950
|
* @param {Integer} [options.flashBufferTime=0] Specifies how long to buffer messages before starting to display the stream (Flash-only)
|
|
13870
|
-
* @param {
|
|
13951
|
+
* @param {string=} options.stripCodecs Comma separated string of codecs which should be stripped from WebRTC SDP (ex. "H264,PCMA,PCMU,G722")
|
|
13871
13952
|
* @param {string=} options.rtmpUrl Rtmp url stream should be forwarded to
|
|
13872
13953
|
* @param {Object=} options.mediaConnectionConstraints Stream specific constraints for underlying RTCPeerConnection
|
|
13873
13954
|
* @param {Boolean=} options.flashShowFullScreenButton Show full screen button in flash
|
|
@@ -13876,6 +13957,7 @@ var createSession = function createSession(options) {
|
|
|
13876
13957
|
* @param {Integer=} options.playoutDelay Time delay between network reception of media and playout
|
|
13877
13958
|
* @param {string=} options.useCanvasMediaStream EXPERIMENTAL: when publish bind browser's media stream to the canvas. It can be useful for image filtering
|
|
13878
13959
|
* @param {string=} options.videoContentHint Video content hint for browser ('detail' by default to maintain resolution), {@link Flashphoner.constants.CONTENT_HINT_TYPE}
|
|
13960
|
+
* @param {Boolean=} options.unmutePlayOnStart Unmute playback on start. May be used after user gesture only, so set 'unmutePlayOnStart: false' for autoplay
|
|
13879
13961
|
* @param {sdpHook} sdpHook The callback that handles sdp from the server
|
|
13880
13962
|
* @returns {Stream} Stream
|
|
13881
13963
|
* @throws {TypeError} Error if no options provided
|
|
@@ -13987,6 +14069,7 @@ var createSession = function createSession(options) {
|
|
|
13987
14069
|
var playoutDelay = options.playoutDelay;
|
|
13988
14070
|
var useCanvasMediaStream = options.useCanvasMediaStream;
|
|
13989
14071
|
var videoContentHint = options.videoContentHint;
|
|
14072
|
+
var unmutePlayOnStart = options.unmutePlayOnStart;
|
|
13990
14073
|
var audioState_;
|
|
13991
14074
|
var videoState_;
|
|
13992
14075
|
var connectionQuality;
|
|
@@ -14163,7 +14246,8 @@ var createSession = function createSession(options) {
|
|
|
14163
14246
|
connectionConstraints: mediaConnectionConstraints,
|
|
14164
14247
|
audioOutputId: audioOutputId,
|
|
14165
14248
|
remoteVideo: remoteVideo,
|
|
14166
|
-
playoutDelay: playoutDelay
|
|
14249
|
+
playoutDelay: playoutDelay,
|
|
14250
|
+
unmutePlayOnStart: unmutePlayOnStart
|
|
14167
14251
|
}, streamRefreshHandlers[id_]).then(function (newConnection) {
|
|
14168
14252
|
mediaConnection = newConnection;
|
|
14169
14253
|
|
|
@@ -19411,6 +19411,9 @@ var getSession = function getSession(id) {
|
|
|
19411
19411
|
* @param {Object=} options.sipOptions Sip configuration
|
|
19412
19412
|
* @param {Object=} options.mediaOptions Media connection configuration
|
|
19413
19413
|
* @param {Integer=} options.timeout Connection timeout in milliseconds
|
|
19414
|
+
* @param {Integer=} options.pingInterval Server ping interval in milliseconds [0]
|
|
19415
|
+
* @param {Integer=} options.receiveProbes A maximum subsequental pings received missing count [0]
|
|
19416
|
+
* @param {Integer=} options.probesInterval Interval to check subsequental pings received [0]
|
|
19414
19417
|
* @returns {Session} Created session
|
|
19415
19418
|
* @throws {Error} Error if API is not initialized
|
|
19416
19419
|
* @throws {TypeError} Error if options.urlServer is not specified
|
|
@@ -19437,6 +19440,8 @@ var createSession = function createSession(options) {
|
|
|
19437
19440
|
var mediaOptions = options.mediaOptions;
|
|
19438
19441
|
var keepAlive = options.keepAlive;
|
|
19439
19442
|
var timeout = options.timeout;
|
|
19443
|
+
var wsPingSender = new WSPingSender(options.pingInterval || 0);
|
|
19444
|
+
var wsPingReceiver = new WSPingReceiver(options.receiveProbes || 0, options.probesInterval || 0);
|
|
19440
19445
|
var connectionTimeout;
|
|
19441
19446
|
var cConfig; //SIP config
|
|
19442
19447
|
|
|
@@ -19554,7 +19559,7 @@ var createSession = function createSession(options) {
|
|
|
19554
19559
|
mediaProviders: Object.keys(MediaProvider),
|
|
19555
19560
|
keepAlive: keepAlive,
|
|
19556
19561
|
authToken: authToken,
|
|
19557
|
-
clientVersion: "2.0.
|
|
19562
|
+
clientVersion: "2.0.212",
|
|
19558
19563
|
clientOSVersion: window.navigator.appVersion,
|
|
19559
19564
|
clientBrowserVersion: window.navigator.userAgent,
|
|
19560
19565
|
msePacketizationVersion: 2,
|
|
@@ -19567,7 +19572,11 @@ var createSession = function createSession(options) {
|
|
|
19567
19572
|
|
|
19568
19573
|
|
|
19569
19574
|
send("connection", cConfig);
|
|
19570
|
-
logger.setConnection(wsConnection);
|
|
19575
|
+
logger.setConnection(wsConnection); // Send ping messages to server to check if connection is still alive #WCS-3410
|
|
19576
|
+
|
|
19577
|
+
wsPingSender.start(); // Check subsequintel pings received from server to check if connection is still alive #WCS-3410
|
|
19578
|
+
|
|
19579
|
+
wsPingReceiver.start();
|
|
19571
19580
|
};
|
|
19572
19581
|
|
|
19573
19582
|
wsConnection.onmessage = function (event) {
|
|
@@ -19583,6 +19592,7 @@ var createSession = function createSession(options) {
|
|
|
19583
19592
|
switch (data.message) {
|
|
19584
19593
|
case 'ping':
|
|
19585
19594
|
send("pong", null);
|
|
19595
|
+
wsPingReceiver.success();
|
|
19586
19596
|
break;
|
|
19587
19597
|
|
|
19588
19598
|
case 'getUserData':
|
|
@@ -19742,7 +19752,11 @@ var createSession = function createSession(options) {
|
|
|
19742
19752
|
sessionStatus = newStatus;
|
|
19743
19753
|
|
|
19744
19754
|
if (sessionStatus == SESSION_STATUS.DISCONNECTED || sessionStatus == SESSION_STATUS.FAILED) {
|
|
19745
|
-
//
|
|
19755
|
+
// Stop pinging server #WCS-3410
|
|
19756
|
+
wsPingSender.stop(); // Stop checking pings received #WCS-3410
|
|
19757
|
+
|
|
19758
|
+
wsPingReceiver.stop(); //remove streams
|
|
19759
|
+
|
|
19746
19760
|
for (var prop in streamRefreshHandlers) {
|
|
19747
19761
|
if (streamRefreshHandlers.hasOwnProperty(prop) && typeof streamRefreshHandlers[prop] === 'function') {
|
|
19748
19762
|
streamRefreshHandlers[prop]({
|
|
@@ -19758,6 +19772,73 @@ var createSession = function createSession(options) {
|
|
|
19758
19772
|
if (callbacks[sessionStatus]) {
|
|
19759
19773
|
callbacks[sessionStatus](session, obj);
|
|
19760
19774
|
}
|
|
19775
|
+
} // Websocket periodic ping sender
|
|
19776
|
+
|
|
19777
|
+
|
|
19778
|
+
function WSPingSender(interval) {
|
|
19779
|
+
this.interval = interval || 0;
|
|
19780
|
+
this.intervalId = null;
|
|
19781
|
+
|
|
19782
|
+
this.start = function () {
|
|
19783
|
+
if (this.interval > 0) {
|
|
19784
|
+
this.intervalId = setInterval(function () {
|
|
19785
|
+
send("ping", null);
|
|
19786
|
+
}, this.interval);
|
|
19787
|
+
}
|
|
19788
|
+
};
|
|
19789
|
+
|
|
19790
|
+
this.stop = function () {
|
|
19791
|
+
if (this.intervalId) {
|
|
19792
|
+
clearInterval(this.intervalId);
|
|
19793
|
+
}
|
|
19794
|
+
};
|
|
19795
|
+
|
|
19796
|
+
return this;
|
|
19797
|
+
} // Websocket ping receive prober
|
|
19798
|
+
|
|
19799
|
+
|
|
19800
|
+
function WSPingReceiver(receiveProbes, probesInterval) {
|
|
19801
|
+
this.maxPings = receiveProbes || 0;
|
|
19802
|
+
this.interval = probesInterval || 0;
|
|
19803
|
+
this.intervalId = null;
|
|
19804
|
+
this.pingsMissing = 0;
|
|
19805
|
+
|
|
19806
|
+
this.start = function () {
|
|
19807
|
+
if (this.maxPings > 0 && this.interval > 0) {
|
|
19808
|
+
var receiver = this;
|
|
19809
|
+
this.intervalId = setInterval(function () {
|
|
19810
|
+
receiver.checkPingsReceived();
|
|
19811
|
+
}, this.interval);
|
|
19812
|
+
}
|
|
19813
|
+
};
|
|
19814
|
+
|
|
19815
|
+
this.stop = function () {
|
|
19816
|
+
if (this.intervalId) {
|
|
19817
|
+
clearInterval(this.intervalId);
|
|
19818
|
+
}
|
|
19819
|
+
|
|
19820
|
+
this.pingsMissing = 0;
|
|
19821
|
+
};
|
|
19822
|
+
|
|
19823
|
+
this.checkPingsReceived = function () {
|
|
19824
|
+
this.pingsMissing++;
|
|
19825
|
+
|
|
19826
|
+
if (this.pingsMissing >= this.maxPings) {
|
|
19827
|
+
this.failure();
|
|
19828
|
+
}
|
|
19829
|
+
};
|
|
19830
|
+
|
|
19831
|
+
this.success = function () {
|
|
19832
|
+
this.pingsMissing = 0;
|
|
19833
|
+
};
|
|
19834
|
+
|
|
19835
|
+
this.failure = function () {
|
|
19836
|
+
logger.info(LOG_PREFIX, "Missing " + this.pingsMissing + " pings from server, connection seems to be down");
|
|
19837
|
+
onSessionStatusChange(SESSION_STATUS.FAILED);
|
|
19838
|
+
wsConnection.close();
|
|
19839
|
+
};
|
|
19840
|
+
|
|
19841
|
+
return this;
|
|
19761
19842
|
}
|
|
19762
19843
|
/**
|
|
19763
19844
|
* @callback sdpHook
|
|
@@ -20622,7 +20703,7 @@ var createSession = function createSession(options) {
|
|
|
20622
20703
|
* @param {HTMLElement} options.display Div element stream should be displayed in
|
|
20623
20704
|
* @param {Object=} options.custom User provided custom object that will be available in REST App code
|
|
20624
20705
|
* @param {Integer} [options.flashBufferTime=0] Specifies how long to buffer messages before starting to display the stream (Flash-only)
|
|
20625
|
-
* @param {
|
|
20706
|
+
* @param {string=} options.stripCodecs Comma separated string of codecs which should be stripped from WebRTC SDP (ex. "H264,PCMA,PCMU,G722")
|
|
20626
20707
|
* @param {string=} options.rtmpUrl Rtmp url stream should be forwarded to
|
|
20627
20708
|
* @param {Object=} options.mediaConnectionConstraints Stream specific constraints for underlying RTCPeerConnection
|
|
20628
20709
|
* @param {Boolean=} options.flashShowFullScreenButton Show full screen button in flash
|
|
@@ -20631,6 +20712,7 @@ var createSession = function createSession(options) {
|
|
|
20631
20712
|
* @param {Integer=} options.playoutDelay Time delay between network reception of media and playout
|
|
20632
20713
|
* @param {string=} options.useCanvasMediaStream EXPERIMENTAL: when publish bind browser's media stream to the canvas. It can be useful for image filtering
|
|
20633
20714
|
* @param {string=} options.videoContentHint Video content hint for browser ('detail' by default to maintain resolution), {@link Flashphoner.constants.CONTENT_HINT_TYPE}
|
|
20715
|
+
* @param {Boolean=} options.unmutePlayOnStart Unmute playback on start. May be used after user gesture only, so set 'unmutePlayOnStart: false' for autoplay
|
|
20634
20716
|
* @param {sdpHook} sdpHook The callback that handles sdp from the server
|
|
20635
20717
|
* @returns {Stream} Stream
|
|
20636
20718
|
* @throws {TypeError} Error if no options provided
|
|
@@ -20742,6 +20824,7 @@ var createSession = function createSession(options) {
|
|
|
20742
20824
|
var playoutDelay = options.playoutDelay;
|
|
20743
20825
|
var useCanvasMediaStream = options.useCanvasMediaStream;
|
|
20744
20826
|
var videoContentHint = options.videoContentHint;
|
|
20827
|
+
var unmutePlayOnStart = options.unmutePlayOnStart;
|
|
20745
20828
|
var audioState_;
|
|
20746
20829
|
var videoState_;
|
|
20747
20830
|
var connectionQuality;
|
|
@@ -20918,7 +21001,8 @@ var createSession = function createSession(options) {
|
|
|
20918
21001
|
connectionConstraints: mediaConnectionConstraints,
|
|
20919
21002
|
audioOutputId: audioOutputId,
|
|
20920
21003
|
remoteVideo: remoteVideo,
|
|
20921
|
-
playoutDelay: playoutDelay
|
|
21004
|
+
playoutDelay: playoutDelay,
|
|
21005
|
+
unmutePlayOnStart: unmutePlayOnStart
|
|
20922
21006
|
}, streamRefreshHandlers[id_]).then(function (newConnection) {
|
|
20923
21007
|
mediaConnection = newConnection;
|
|
20924
21008
|
|