-
Notifications
You must be signed in to change notification settings - Fork 0
/
ControlPointManager.vb
1542 lines (1496 loc) · 87 KB
/
ControlPointManager.vb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Imports System.Net
Imports System.Net.Sockets
Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Threading
Imports System.Xml
Partial Public Class Plugin
Private Shared ReadOnly renderingDevices As New List(Of MediaRendererDevice)
Private Shared activeRenderingDevice As MediaRendererDevice
Private Class ControlPointManager
Private listenerThreads() As Thread = Nothing
Private listenerSockets() As Socket = Nothing
Private started As Boolean = False
Private ReadOnly resourceLock As New Object
Private shutdownSearchTimer As Timer = Nothing
Public Sub Dispose()
[Stop]()
End Sub
Public Sub Start()
Try
''server.HttpServer.AddRoute("NOTIFY", "/eventSub", New HttpRouteDelegate(AddressOf ProcessEventSubscription))
If Settings.EnablePlayToDevice Then
ExecuteSearchForRenderingDevices()
End If
Catch ex As Exception
LogError(ex, "ControlPointManager:Start")
End Try
End Sub
Public Sub [Stop]()
AudioEncoder.StopEncode()
ShutdownSearchThreads(Nothing)
started = False
SyncLock renderingDevices
For Each device As MediaRendererDevice In renderingDevices
device.Dispose()
Next device
renderingDevices.Clear()
activeRenderingDevice = Nothing
End SyncLock
End Sub
Public Sub Restart()
If activeRenderingDevice IsNot Nothing Then
activeRenderingDevice.StopPlayback(True)
End If
If Not Settings.EnablePlayToDevice Then
[Stop]()
ElseIf Not started Then
ExecuteSearchForRenderingDevices()
End If
End Sub
Private Sub ExecuteSearchForRenderingDevices()
listenerThreads = New Thread(hostAddresses.Length - 1) {}
listenerSockets = New Socket(hostAddresses.Length - 1) {}
Dim broadcastGroup As IPAddress = IPAddress.Parse("239.255.255.250")
Dim remoteEndPoint As New IPEndPoint(broadcastGroup, 1900)
Dim request() As Byte = Encoding.UTF8.GetBytes("M-SEARCH * HTTP/1.1" & ControlChars.CrLf & "HOST: 239.255.255.250:1900" & ControlChars.CrLf & "ST:urn:schemas-upnp-org:device:MediaRenderer:1" & ControlChars.CrLf & "MAN: ""ssdp:discover""" & ControlChars.CrLf & "MX: 1" & ControlChars.CrLf & ControlChars.CrLf)
For index As Integer = 0 To hostAddresses.Length - 1
Dim address As IPAddress = hostAddresses(index)
Dim listenerSocket As New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
listenerSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, True)
listenerSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, True)
listenerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, New MulticastOption(broadcastGroup, address))
listenerSocket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 2)
listenerSocket.Bind(New IPEndPoint(address, 0))
listenerSockets(index) = listenerSocket
Dim thread As New Thread(New ParameterizedThreadStart(AddressOf Listen)) With {
.IsBackground = True,
.Priority = ThreadPriority.BelowNormal
}
listenerThreads(index) = thread
thread.Start(listenerSocket)
listenerSocket.SendTo(request, remoteEndPoint)
Next index
started = True
shutdownSearchTimer = New Timer(AddressOf ShutdownSearchThreads, Nothing, 30000, Timeout.Infinite)
End Sub
Private Sub ShutdownSearchThreads(state As Object)
Try
SyncLock resourceLock
If shutdownSearchTimer IsNot Nothing Then
shutdownSearchTimer.Dispose()
shutdownSearchTimer = Nothing
End If
If listenerSockets IsNot Nothing Then
For index As Integer = 0 To listenerSockets.Length - 1
If listenerSockets(index) IsNot Nothing Then
Try
closesocket(listenerSockets(index).Handle)
listenerSockets(index).Close()
listenerThreads(index).Join()
Catch
End Try
End If
Next index
listenerSockets = Nothing
listenerThreads = Nothing
End If
End SyncLock
Catch
End Try
End Sub
Private Sub Listen(parameters As Object)
Dim socket As Socket = DirectCast(parameters, Socket)
Dim buffer As Byte() = New Byte(1023) {}
Dim length As Integer
Do
Dim receivePoint As EndPoint = New IPEndPoint(IPAddress.Any, 0)
Try
length = socket.ReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, receivePoint)
Catch
Exit Do
End Try
Try
If TypeOf receivePoint Is IPEndPoint AndAlso DirectCast(receivePoint, IPEndPoint).Port = Settings.ServerPort Then
' ignore
Else
ProcessMessageHeader(receivePoint, Encoding.ASCII.GetString(buffer, 0, length).Split(New String() {ControlChars.CrLf}, StringSplitOptions.RemoveEmptyEntries), "st:")
End If
Catch ex As Exception
LogError(ex, "ControlListen")
End Try
Loop
End Sub
Public Sub ProcessMessageHeader(receivingSocket As EndPoint, header() As String, serviceKey As String)
If Settings.EnablePlayToDevice Then
Dim deviceLocationUrl As Uri = Nothing
Dim isMediaRenderer As Boolean = False
Dim notificationSubType As String = If(serviceKey <> "st:", Nothing, "ssdp:alive")
Dim udn As String = Nothing
For index As Integer = 0 To header.Length - 1
If header(index).StartsWith("location:", StringComparison.OrdinalIgnoreCase) Then
deviceLocationUrl = New Uri(header(index).Substring(9).Trim())
ElseIf header(index).StartsWith("usn:", StringComparison.OrdinalIgnoreCase) Then
Dim usn As String = header(index).Substring(4).Trim()
If Not usn.EndsWith("::urn:schemas-upnp-org:device:MediaRenderer:1", StringComparison.OrdinalIgnoreCase) AndAlso Not usn.EndsWith("::urn:schemas-upnp-org:device:MediaRenderer:2", StringComparison.OrdinalIgnoreCase) Then
udn = Nothing
Else
udn = usn.Substring(0, usn.Length - 45)
End If
ElseIf header(index).StartsWith("nts:", StringComparison.OrdinalIgnoreCase) Then
notificationSubType = header(index).Substring(4).Trim()
ElseIf header(index).StartsWith(serviceKey, StringComparison.OrdinalIgnoreCase) Then
isMediaRenderer = (String.Compare(header(index).Substring(3).Trim(), "urn:schemas-upnp-org:device:MediaRenderer:1", StringComparison.OrdinalIgnoreCase) = 0 OrElse String.Compare(header(index).Substring(3).Trim(), "urn:schemas-upnp-org:device:MediaRenderer:2", StringComparison.OrdinalIgnoreCase) = 0)
End If
Next index
Dim receivingAddress As String = "unknown address"
If Settings.LogDebugInfo AndAlso TypeOf receivingSocket Is IPEndPoint Then
receivingAddress = DirectCast(receivingSocket, IPEndPoint).Address.ToString()
End If
'If Settings.LogDebugInfo Then
' Dim headerText As New StringBuilder(512)
' Try
' If Not TypeOf receivingSocket Is IPEndPoint Then
' headerText.Append("unknown remote point")
' Else
' headerText.Append(DirectCast(receivingSocket, IPEndPoint).Address.ToString())
' End If
' Catch
' End Try
' headerText.Append("; key=")
' headerText.Append(serviceKey)
' headerText.Append("; ")
' For index As Integer = 0 To header.Length - 1
' headerText.Append(header(index))
' headerText.Append("; ")
' Next index
' headerText.Append("nts=")
' headerText.Append(If(notificationSubType Is Nothing, "null", notificationSubType))
' headerText.Append("; udn=")
' headerText.Append(If(udn Is Nothing, "null", udn))
' LogInformation("ProcessMessageHeader", headerText.ToString())
'End If
If udn IsNot Nothing Then
Dim devicesChanged As Boolean = False
SyncLock renderingDevices
Dim deviceIndex As Integer = -1
For index As Integer = 0 To renderingDevices.Count - 1
If renderingDevices(index).Udn.Contains(udn) Then
deviceIndex = index
Exit For
End If
Next index
If notificationSubType = "ssdp:alive" Then
If deviceIndex = -1 AndAlso isMediaRenderer AndAlso deviceLocationUrl IsNot Nothing Then
Dim device As New MediaRendererDevice(deviceLocationUrl)
If device.IsValidRenderer Then
devicesChanged = True
renderingDevices.Add(device)
End If
LogInformation("ProcessMessage " & receivingAddress, "device '" & udn & ":" & device.FriendlyName & "',valid=" & device.IsValidRenderer)
End If
ElseIf notificationSubType = "ssdp:byebye" AndAlso deviceIndex <> -1 Then
devicesChanged = True
If activeRenderingDevice IsNot Nothing AndAlso activeRenderingDevice.Udn.Contains(udn) Then
activeRenderingDevice = Nothing
End If
Dim device As MediaRendererDevice = renderingDevices(deviceIndex)
device.Activate(False)
renderingDevices.RemoveAt(deviceIndex)
LogInformation("ProcessMessage " & receivingAddress, "device '" & udn & ":" & device.FriendlyName & "' disconnected")
End If
End SyncLock
If devicesChanged Then
mbApiInterface.MB_SendNotification(CallbackType.RenderingDevicesChanged)
End If
End If
End If
End Sub
''Private Sub ProcessEventSubscription(request As HttpRequest)
'' Dim sid As String
'' If request.Headers.TryGetValue("SID", sid) Then
'' SyncLock renderingDevices
'' Try
'' If activeRenderingDevice IsNot Nothing Then
'' activeRenderingDevice.ProcessEventSubscription(sid, request)
'' End If
'' Catch
'' End Try
'' End SyncLock
'' End If
'' Dim response As HttpResponse = request.Response
'' response.StateCode = 200
'' response.SendHeaders()
''End Sub
<DllImport("ws2_32.dll", CharSet:=CharSet.Unicode)> _
Private Shared Function closesocket(socketHandle As IntPtr) As Integer
End Function
End Class ' ControlPointManager
Private Class MediaRendererDevice
Public ReadOnly FriendlyName As String
Public ReadOnly Udn As New HashSet(Of String)(StringComparer.Ordinal)
Private isActive As Boolean = False
Private ReadOnly modelDescription As String = ""
Private ReadOnly supportSetNextInQueue As Boolean = False
'Private supportPlayPause As Boolean = False
'Private supportPlayNext As Boolean = False
'Private supportPlayPrev As Boolean = False
'Private supportPlaySeek As Boolean = False
''Private avTransportEventSid As String
''Private avTransportEventTimeout As Integer
''Private renderingControlEventSid As String
''Private renderingControlEventTimeout As Integer
Private deviceMinVolume As UInteger = 0
Private deviceMaxVolume As UInteger = 100
Private currentVolume As UInteger
Private currentMute As Boolean
Private currentErrorCount As Integer = 0
Private currentPlayState As PlayState = PlayState.Undefined
Private currentPlaySpeed As Integer = 1
'Private currentTransportStatus As String
Private currentPlayStartTimeEstimated As Boolean
Private currentPlayStartTicks As Long
Private currentTrackDurationTicks As Long
Private currentPlayPositionMs As Integer
'Private currentPlayUrl As String
'Private nextPlayUrl As String
Private lastUserInitiatedStop As Long = 0
Private queueNextTrackPending As Boolean = False
'Private queueNextFailedCount As Integer = 0
Private lastServerHeader As String
Private directory As ItemManager = Nothing
Private streamingProfile As StreamingProfile = Nothing
Private ReadOnly resourceLock As New Object
Private playToMode As Boolean = False
Private statusTimerInterval As Integer = 500
'Private ReadOnly deviceLocationUrl As Uri
Private ReadOnly connectionManagerUrl As Uri
'Private ReadOnly connectionManagerEventUrl As Uri
Private ReadOnly renderingControlUrl As Uri
Private ReadOnly renderingControlSCPDUri As Uri
Private ReadOnly renderingControlEventUrl As Uri
''Private ReadOnly renderingControlNotifyTimer As New Timer(AddressOf OnRenderingControlResubscribe, Nothing, Timeout.Infinite, Timeout.Infinite)
Private ReadOnly avTransportControlUrl As Uri
Private ReadOnly avTransportSCPDUri As Uri
Private ReadOnly avTransportEventUrl As Uri
''Private ReadOnly avTransportNotifyTimer As New Timer(AddressOf OnAvTransportResubscribe, Nothing, Timeout.Infinite, Timeout.Infinite)
Private ReadOnly avTransportStatusTimer As New Timer(AddressOf OnAvTransportStatusCheck, Nothing, Timeout.Infinite, Timeout.Infinite)
Private Shared ReadOnly userAgent As String = "User-Agent: MusicBee UPnP Plugin" & ControlChars.CrLf
Public Sub New(deviceLocationUrl As Uri)
'Me.deviceLocationUrl = deviceLocationUrl
Dim xml As String = GetXmlDocument(deviceLocationUrl)
For retry As Integer = 1 To 2
Try
If retry = 2 Then
xml = GetCleanedXml(xml)
End If
'LogInformation("NewMediaRendererDevice:" & deviceLocationUrl.ToString(), xml)
Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element Then
Dim name As String = reader.Name
If String.Compare(name, "friendlyName", StringComparison.OrdinalIgnoreCase) = 0 Then
If FriendlyName IsNot Nothing Then
reader.ReadString()
Else
FriendlyName = reader.ReadString()
End If
ElseIf String.Compare(name, "modelDescription", StringComparison.OrdinalIgnoreCase) = 0 Then
If modelDescription IsNot Nothing Then
reader.ReadString()
Else
modelDescription = reader.ReadString()
End If
ElseIf String.Compare(name, "UDN", StringComparison.OrdinalIgnoreCase) = 0 Then
Udn.Add(reader.ReadString())
ElseIf String.Compare(name, "service", StringComparison.OrdinalIgnoreCase) = 0 Then
Using serviceReader As XmlReader = reader.ReadSubtree()
Dim serviceType As String
Do While serviceReader.Read()
If serviceReader.NodeType = XmlNodeType.Element Then
name = serviceReader.Name
If String.Compare(name, "serviceType", StringComparison.OrdinalIgnoreCase) = 0 Then
serviceType = serviceReader.ReadString()
ElseIf serviceType IsNot Nothing Then
If String.Compare(name, "SCPDURL", StringComparison.OrdinalIgnoreCase) = 0 Then
':1
If serviceType.StartsWith("urn:schemas-upnp-org:service:AVTransport:", StringComparison.OrdinalIgnoreCase) Then
avTransportSCPDUri = New Uri(deviceLocationUrl, serviceReader.ReadString())
ElseIf serviceType.StartsWith("urn:schemas-upnp-org:service:RenderingControl:", StringComparison.OrdinalIgnoreCase) Then
renderingControlSCPDUri = New Uri(deviceLocationUrl, serviceReader.ReadString())
End If
ElseIf String.Compare(name, "controlURL", StringComparison.OrdinalIgnoreCase) = 0 Then
':1
If serviceType.StartsWith("urn:schemas-upnp-org:service:ConnectionManager:", StringComparison.OrdinalIgnoreCase) Then
connectionManagerUrl = New Uri(deviceLocationUrl, serviceReader.ReadString())
ElseIf serviceType.StartsWith("urn:schemas-upnp-org:service:AVTransport:", StringComparison.OrdinalIgnoreCase) Then
avTransportControlUrl = New Uri(deviceLocationUrl, serviceReader.ReadString())
ElseIf serviceType.StartsWith("urn:schemas-upnp-org:service:RenderingControl:", StringComparison.OrdinalIgnoreCase) Then
renderingControlUrl = New Uri(deviceLocationUrl, serviceReader.ReadString())
End If
ElseIf String.Compare(name, "eventSubURL", StringComparison.OrdinalIgnoreCase) = 0 Then
':1
If serviceType.StartsWith("urn:schemas-upnp-org:service:AVTransport:", StringComparison.OrdinalIgnoreCase) Then
avTransportEventUrl = New Uri(deviceLocationUrl, serviceReader.ReadString())
ElseIf serviceType.StartsWith("urn:schemas-upnp-org:service:RenderingControl:", StringComparison.OrdinalIgnoreCase) Then
renderingControlEventUrl = New Uri(deviceLocationUrl, serviceReader.ReadString())
End If
End If
End If
End If
Loop
End Using
End If
End If
Loop
End Using
Exit For
Catch ex As XmlException
If retry = 2 Then
Throw LogError(ex, "NewMediaRendererDevice:" & deviceLocationUrl.ToString(), xml)
End If
End Try
Next retry
End Sub
Public Sub Dispose()
If isActive Then
StopPlayback()
If mbApiInterface.Player_GetPlayState() <> Plugin.PlayState.Stopped Then
mbApiInterface.Player_Stop()
End If
Activate(False)
End If
''avTransportNotifyTimer.Dispose()
''renderingControlNotifyTimer.Dispose()
End Sub
Public ReadOnly Property IsValidRenderer() As Boolean
Get
Return (avTransportSCPDUri IsNot Nothing AndAlso avTransportEventUrl IsNot Nothing AndAlso connectionManagerUrl IsNot Nothing AndAlso renderingControlEventUrl IsNot Nothing)
End Get
End Property
Public Function Activate(active As Boolean) As Boolean
Dim xml As String = Nothing
Try
If active Then
If isActive Then
Activate(False)
End If
If directory Is Nothing Then
Dim requestHeaders As New Dictionary(Of String, String) From {
{"User-Agent", modelDescription & "|" & lastServerHeader}
}
streamingProfile = Settings.GetStreamingProfile(requestHeaders)
directory = ItemManager.GetItemManager(requestHeaders)
If lastServerHeader IsNot Nothing AndAlso lastServerHeader.IndexOf("Platinum", StringComparison.OrdinalIgnoreCase) <> -1 Then
directory.DisablePcmTimeSeek = True
End If
End If
xml = GetXmlDocument(avTransportSCPDUri)
For retry As Integer = 1 To 2
Try
If retry = 2 Then
xml = GetCleanedXml(xml)
End If
Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element AndAlso String.Compare(reader.Name, "Action", StringComparison.OrdinalIgnoreCase) = 0 Then
Using actionReader As XmlReader = reader.ReadSubtree()
Do While actionReader.Read()
If actionReader.NodeType = XmlNodeType.Element AndAlso String.Compare(actionReader.Name, "name", StringComparison.OrdinalIgnoreCase) = 0 Then
Dim name As String = actionReader.ReadString()
If String.Compare(name, "SetNextAVTransportURI", StringComparison.OrdinalIgnoreCase) = 0 AndAlso Settings.EnablePlayToSetNext Then
'supportSetNextInQueue = True
'ElseIf String.Compare(name, "Pause", StringComparison.OrdinalIgnoreCase) = 0 Then
' supportPlayPause = True
'ElseIf String.Compare(name, "Next", StringComparison.OrdinalIgnoreCase) = 0 Then
' supportPlayNext = True
'ElseIf String.Compare(name, "Previous", StringComparison.OrdinalIgnoreCase) = 0 Then
' supportPlayPrev = True
'ElseIf String.Compare(name, "Seek", StringComparison.OrdinalIgnoreCase) = 0 Then
' supportPlaySeek = True
End If
End If
Loop
End Using
End If
Loop
End Using
Exit For
Catch ex As XmlException
If retry = 2 Then Throw
End Try
Next retry
Try
xml = GetXmlDocument(renderingControlSCPDUri)
For retry As Integer = 1 To 2
Try
If retry = 2 Then
xml = GetCleanedXml(xml)
End If
Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element AndAlso String.Compare(reader.Name, "stateVariable", StringComparison.OrdinalIgnoreCase) = 0 Then
Dim isVolumeNode As Boolean = False
Using variableReader As XmlReader = reader.ReadSubtree()
Do While variableReader.Read()
If variableReader.NodeType = XmlNodeType.Element Then
If String.Compare(variableReader.Name, "name", StringComparison.OrdinalIgnoreCase) = 0 Then
If String.Compare(variableReader.ReadString(), "Volume", StringComparison.OrdinalIgnoreCase) = 0 Then
isVolumeNode = True
End If
ElseIf isVolumeNode Then
If String.Compare(variableReader.Name, "minimum", StringComparison.OrdinalIgnoreCase) = 0 Then
UInteger.TryParse(variableReader.ReadString(), deviceMinVolume)
ElseIf String.Compare(variableReader.Name, "maximum", StringComparison.OrdinalIgnoreCase) = 0 Then
UInteger.TryParse(variableReader.ReadString(), deviceMaxVolume)
End If
End If
End If
Loop
End Using
If isVolumeNode Then
Exit Do
End If
End If
Loop
End Using
Exit For
Catch ex As XmlException
If retry = 2 Then Throw
End Try
Next retry
Catch ex As Exception
LogError(ex, "Activate:GetVolumeRange")
End Try
Dim values() As String
Using reader As New XmlTextReader(PostSoapRequest(connectionManagerUrl, "GetProtocolInfo", "urn:schemas-upnp-org:service:ConnectionManager:1"), XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element AndAlso String.Compare(reader.Name, "Sink", StringComparison.OrdinalIgnoreCase) = 0 Then
Dim mimeTypeList As String = reader.ReadString()
LogInformation("Activate", FriendlyName & ":" & mimeTypeList)
values = mimeTypeList.Split(New Char() {","c}, StringSplitOptions.RemoveEmptyEntries)
Dim supportedMimeTypes() As String = New String(values.Length - 1) {}
For index As Integer = 0 To values.Length - 1
Dim sections() As String = values(index).Split(":"c)
If sections.Length <= 2 Then
supportedMimeTypes(index) = "error"
LogInformation("Activate", "invalid mime type: " & values(index))
Else
supportedMimeTypes(index) = sections(2)
End If
Next index
directory.SupportedMimeTypes = supportedMimeTypes
Exit Do
End If
Loop
End Using
''values = SubscribeSoapRequest(avTransportEventUrl, primaryHostAddress & "/eventSub", Nothing)
''avTransportEventSid = values(0)
''If Integer.TryParse(values(1), avTransportEventTimeout) Then
'' avTransportEventTimeout *= 1000
''Else
'' avTransportEventTimeout = Timeout.Infinite
''End If
''avTransportNotifyTimer.Change(avTransportEventTimeout, avTransportEventTimeout)
''values = SubscribeSoapRequest(renderingControlEventUrl, PrimaryHostUrl & "/eventSub", Nothing)
''renderingControlEventSid = values(0)
''If Integer.TryParse(values(1), renderingControlEventTimeout) Then
'' renderingControlEventTimeout *= 1000
''Else
'' renderingControlEventTimeout = Timeout.Infinite
''End If
''renderingControlNotifyTimer.Change(renderingControlEventTimeout, renderingControlEventTimeout)
StopPlayback()
GetPlayStateInformation()
InitialiseVolume()
isActive = True
Return (directory.SupportedMimeTypes IsNot Nothing)
ElseIf isActive Then
isActive = False
playToMode = False
avTransportStatusTimer.Change(Timeout.Infinite, Timeout.Infinite)
If currentPlayState <> PlayState.Stopped Then
currentPlayState = PlayState.Stopped
mbApiInterface.Player_Stop()
End If
''avTransportNotifyTimer.Change(Timeout.Infinite, Timeout.Infinite)
''UnsubscribeSoapRequest(avTransportEventUrl, avTransportEventSid)
''avTransportEventSid = Nothing
''renderingControlNotifyTimer.Change(Timeout.Infinite, Timeout.Infinite)
''UnsubscribeSoapRequest(renderingControlEventUrl, renderingControlEventSid)
''renderingControlEventSid = Nothing
End If
Return True
Catch ex As XmlException
LogError(ex, "Activate:" & active & ":" & avTransportSCPDUri.ToString(), xml)
Return False
Catch ex As Exception
LogError(ex, "Activate:" & active)
Return False
End Try
End Function
''Public Sub ProcessEventSubscription(sid As String, request As HttpRequest)
'' If isActive AndAlso String.Compare(sid, renderingControlEventSid, StringComparison.OrdinalIgnoreCase) = 0 Then '' OrElse String.Compare(sid, avTransportEventSid, StringComparison.OrdinalIgnoreCase) = 0) Then
'' Using stream As IO.MemoryStream = request.GetContent(), _
'' reader As New XmlTextReader(stream)
'' Do While reader.Read()
'' If reader.NodeType = XmlNodeType.Element AndAlso String.Compare(reader.Name, "LastChange", StringComparison.OrdinalIgnoreCase) = 0 Then
'' Dim xml As String = reader.ReadString()
'' If xml.IndexOf("VolumeDB ", StringComparison.OrdinalIgnoreCase) <> -1 OrElse xml.IndexOf("Mute ", StringComparison.OrdinalIgnoreCase) <> -1 Then
'' 'LogInformation("Volume", xml)
'' Using lastChangeReader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
'' Do While lastChangeReader.Read()
'' If lastChangeReader.NodeType = XmlNodeType.Element Then
'' If String.Compare(lastChangeReader.Name, "VolumeDB", StringComparison.OrdinalIgnoreCase) = 0 AndAlso String.Compare(lastChangeReader.GetAttribute(0), "Master", StringComparison.OrdinalIgnoreCase) = 0 Then
'' Dim value As Integer
'' If Integer.TryParse(lastChangeReader.GetAttribute("val"), value) Then
'' currentVolumeDb = value / 256.0F
'' Debug.WriteLine("cur vol event=" & currentVolumeDb)
'' Dim newVolume As Single = (currentVolumeDb - deviceMinVolumeDb) / (deviceMaxVolumeDb - deviceMinVolumeDb)
'' mbApiInterface.Player_SetVolume(newVolume)
'' End If
'' ElseIf String.Compare(lastChangeReader.Name, "Mute", StringComparison.OrdinalIgnoreCase) = 0 AndAlso String.Compare(lastChangeReader.GetAttribute(0), "Master", StringComparison.OrdinalIgnoreCase) = 0 Then
'' currentMute = (lastChangeReader.GetAttribute("val") = "1")
'' mbApiInterface.Player_SetMute(currentMute)
'' End If
'' '' If String.Compare(lastChangeReader.Name, "TransportState", StringComparison.OrdinalIgnoreCase) = 0 Then
'' '' Dim syncPlayState As Boolean = False
'' '' SyncLock resourceLock
'' '' Dim oldPlayState As PlayState = currentPlayState
'' '' Select Case lastChangeReader.GetAttribute("val")
'' '' Case "STOPPED"
'' '' currentPlayState = PlayState.Stopped
'' '' Case "PLAYING"
'' '' currentPlayState = PlayState.Playing
'' '' Case "PAUSED_PLAYBACK"
'' '' currentPlayState = PlayState.Paused
'' '' Case "TRANSITIONING"
'' '' currentPlayState = PlayState.Loading
'' '' End Select
'' '' Debug.WriteLine(Now.ToString() & ":event=" & currentPlayState.ToString & ",was=" & oldPlayState.ToString())
'' '' If currentPlayState <> oldPlayState Then
'' '' syncPlayState = ProcessNewPlayState(oldPlayState)
'' '' End If
'' '' End SyncLock
'' '' If syncPlayState Then
'' '' SyncNewPlayState()
'' '' End If
'' '' ElseIf String.Compare(lastChangeReader.Name, "TransportStatus", StringComparison.OrdinalIgnoreCase) = 0 Then
'' '' currentTransportStatus = lastChangeReader.ReadString()
'' '' Else
'' '' 'CurrentPlayMode
'' '' 'CurrentTrackURI
'' '' 'CurrentTrackDuration val=
'' '' 'CurrentMediaDuration val=
'' '' 'TransportState
'' '' 'CurrentTrackMetaData val=
'' '' 'CurrentTrack val="0"
'' '' 'NextAVTransportURI val="NOT_IMPLEMENTED"
'' '' 'NumberOfTracks val="0"
'' '' 'TransportStatus val="OK"
'' '' 'TransportPlaySpeed val="1"
'' '' 'Volume Channel="Master" val="65"
'' '' 'Mute Channel="Master" val="0"
'' '' 'VolumeDB Channel="Master" val="-1578"
'' '' End If
'' End If
'' Loop
'' End Using
'' End If
'' Exit Do
'' End If
'' Loop
'' End Using
'' End If
''End Sub
Private Function ProcessNewPlayState(oldPlayState As PlayState) As Boolean
If Not playToMode Then
Return False
End If
Dim playNextTrack As Boolean = False
Select Case currentPlayState
Case PlayState.Stopped
avTransportStatusTimer.Change(Timeout.Infinite, Timeout.Infinite)
currentPlayStartTimeEstimated = True
currentPlayPositionMs = 0
Dim stopInvokedByUser As Boolean = ((DateTime.UtcNow.Ticks - lastUserInitiatedStop) < 5000 * TimeSpan.TicksPerMillisecond)
lastUserInitiatedStop = 0
queueNextTrackPending = False
If (oldPlayState = PlayState.Playing OrElse oldPlayState = PlayState.Loading) AndAlso Not supportSetNextInQueue AndAlso Not stopInvokedByUser Then
currentPlayStartTicks = Long.MaxValue
If mbApiInterface.Player_PlayNextTrack() Then
Return False
End If
End If
Return True
Case PlayState.Playing
If currentPlayStartTimeEstimated Then
currentPlayPositionMs = 0
currentPlayStartTicks = DateTime.UtcNow.Ticks
currentPlayStartTimeEstimated = False
Else
currentPlayStartTicks = DateTime.UtcNow.Ticks - currentPlayPositionMs * TimeSpan.TicksPerMillisecond
End If
avTransportStatusTimer.Change(0, statusTimerInterval)
Return True
Case PlayState.Paused
currentPlayPositionMs = CInt((DateTime.UtcNow.Ticks - currentPlayStartTicks) \ TimeSpan.TicksPerMillisecond)
avTransportStatusTimer.Change(Timeout.Infinite, Timeout.Infinite)
Return True
End Select
Return False
End Function
Private Sub SyncNewPlayState()
Dim mbPlayState As PlayState = mbApiInterface.Player_GetPlayState()
If currentPlayState <> mbPlayState Then
LogInformation("SyncNewPlayState", currentPlayState.ToString & ",mb=" & mbPlayState.ToString())
Select Case currentPlayState
Case PlayState.Stopped
If mbPlayState <> PlayState.Loading Then
mbApiInterface.Player_Stop()
End If
Case PlayState.Paused
If mbPlayState = PlayState.Playing Then
mbApiInterface.Player_PlayPause()
End If
Case PlayState.Playing
If mbPlayState = PlayState.Paused Then
mbApiInterface.Player_PlayPause()
End If
End Select
End If
End Sub
Public ReadOnly Property PlayState() As PlayState
Get
Return currentPlayState
End Get
End Property
Public ReadOnly Property PlayPositionMs() As Integer
Get
Select Case currentPlayState
Case PlayState.Playing, Plugin.PlayState.Loading
If currentPlayStartTimeEstimated Then
Return currentPlayPositionMs
Else
Return CInt((DateTime.UtcNow.Ticks - currentPlayStartTicks) \ TimeSpan.TicksPerMillisecond)
End If
Case PlayState.Paused
Return currentPlayPositionMs
End Select
Return 0
End Get
End Property
Private Function GetPlayStateInformation() As Exception
Dim xml As String = Nothing
Try
xml = PostSoapRequest(avTransportControlUrl, "GetTransportInfo", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID"}, New String() {"0"})
Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element Then
If String.Compare(reader.Name, "CurrentTransportState", StringComparison.OrdinalIgnoreCase) = 0 Then
Dim value As String = reader.ReadString()
If value IsNot Nothing Then
Select Case value.ToUpper()
Case "STOPPED", "NO_MEDIA_PRESENT"
currentPlayState = PlayState.Stopped
Case "PLAYING"
currentPlayState = PlayState.Playing
Case "PAUSED_PLAYBACK"
currentPlayState = PlayState.Paused
Case "TRANSITIONING"
currentPlayState = PlayState.Loading
End Select
End If
'ElseIf String.Compare(reader.Name, "CurrentTransportStatus", StringComparison.OrdinalIgnoreCase) = 0 Then
' currentTransportStatus = reader.ReadString()
ElseIf String.Compare(reader.Name, "CurrentSpeed", StringComparison.OrdinalIgnoreCase) = 0 Then
Integer.TryParse(reader.ReadString(), currentPlaySpeed)
End If
End If
Loop
End Using
Return Nothing
Catch ex As XmlException
Return LogError(ex, "GetTransportInfo", xml)
Catch ex As Exception
Return ex
End Try
End Function
Private Function GetPlayPositionInformation() As Boolean
Dim xml As String = Nothing
Try
xml = PostSoapRequest(avTransportControlUrl, "GetPositionInfo", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID"}, New String() {"0"})
Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element Then
If String.Compare(reader.Name, "TrackURI", StringComparison.OrdinalIgnoreCase) = 0 Then
'currentPlayUrl = reader.ReadString()
ElseIf String.Compare(reader.Name, "RelTime", StringComparison.OrdinalIgnoreCase) = 0 Then
'can be: NOT_IMPLEMENTED
Dim value As TimeSpan
If Not TimeSpan.TryParse(reader.ReadString(), value) Then
currentPlayPositionMs = 0
Else
currentPlayPositionMs = CInt(value.Ticks \ TimeSpan.TicksPerMillisecond)
End If
Else
'Track
'TrackDuration
'TrackMetaData
End If
End If
Loop
End Using
Return True
Catch ex As Exception
LogError(ex, "GetPositionInfo", xml)
End Try
Return False
End Function
Private Sub InitialiseVolume()
Dim xml As String = Nothing
Try
xml = PostSoapRequest(renderingControlUrl, "GetMute", "urn:schemas-upnp-org:service:RenderingControl:1", New String() {"InstanceID", "Channel"}, New String() {"0", "Master"})
Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element Then
If String.Compare(reader.Name, "CurrentMute", StringComparison.OrdinalIgnoreCase) = 0 Then
currentMute = (reader.ReadString() = "1")
mbApiInterface.Player_SetMute(currentMute)
Exit Do
End If
End If
Loop
End Using
Catch ex As Exception
LogError(ex, "SetMusicBeeMute", xml)
End Try
Try
xml = PostSoapRequest(renderingControlUrl, "GetVolume", "urn:schemas-upnp-org:service:RenderingControl:1", New String() {"InstanceID", "Channel"}, New String() {"0", "Master"})
Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
Do While reader.Read()
If reader.NodeType = XmlNodeType.Element Then
Dim value As UInteger
If String.Compare(reader.Name, "CurrentVolume", StringComparison.OrdinalIgnoreCase) = 0 Then
Dim volumeText As String = reader.ReadString()
If Not UInteger.TryParse(volumeText, value) Then
LogInformation("SetMusicBeeVolume", "invalid volume=" & volumeText)
Else
currentVolume = value
mbApiInterface.Player_SetVolume(CSng(currentVolume / deviceMaxVolume))
End If
Exit Do
End If
End If
Loop
End Using
Catch ex As Exception
LogError(ex, "SetMusicBeeVolume", xml)
End Try
End Sub
Public Sub SetVolume(value As Single)
Try
Dim newVolume As UInteger = CUInt(value * deviceMaxVolume)
If newVolume <> currentVolume Then
Dim statusCode As String = PostSoapRequest(renderingControlUrl, "SetVolume", "urn:schemas-upnp-org:service:RenderingControl:1", New String() {"InstanceID", "Channel", "DesiredVolume"}, New String() {"0", "Master", newVolume.ToString()}, True)
If statusCode = "200" Then
currentVolume = newVolume
Else
LogInformation("SetVolume", "status=" & statusCode)
End If
End If
Catch ex As Exception
LogError(ex, "SetVolume")
End Try
End Sub
Public Sub SetMute(value As Boolean)
If value <> currentMute Then
Try
Dim statusCode As String = PostSoapRequest(renderingControlUrl, "SetMute", "urn:schemas-upnp-org:service:RenderingControl:1", New String() {"InstanceID", "Channel", "DesiredMute"}, New String() {"0", "Master", If(Not value, "0", "1")}, True)
If statusCode = "200" Then
currentMute = value
Else
LogInformation("SetMute", "status=" & statusCode)
End If
Catch ex As Exception
LogError(ex, "SetMute")
End Try
End If
End Sub
''Private Sub GetMediaInfo()
'' ' needed for playlists - only updates when the entire playlist is done
'' Try
'' Dim xml As String = PostSoapRequest(avTransportControlUrl, "GetMediaInfo", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID"}, New String() {"0"})
'' Using reader As New XmlTextReader(xml, XmlNodeType.Document, Nothing)
'' If reader.NodeType = XmlNodeType.Element Then
'' 'If String.Compare(reader.Name, "NrTracks", StringComparison.OrdinalIgnoreCase) = 0 Then
'' 'ElseIf String.Compare(reader.Name, "MediaDuration", StringComparison.OrdinalIgnoreCase) = 0 Then
'' 'ElseIf String.Compare(reader.Name, "CurrentURI", StringComparison.OrdinalIgnoreCase) = 0 Then
'' 'ElseIf String.Compare(reader.Name, "CurrentURIMetaData", StringComparison.OrdinalIgnoreCase) = 0 Then
'' 'ElseIf String.Compare(reader.Name, "NextURIMetaData", StringComparison.OrdinalIgnoreCase) = 0 Then
'' If String.Compare(reader.Name, "NextURI", StringComparison.OrdinalIgnoreCase) = 0 Then
'' nextPlayUrl = reader.ReadString()
'' If String.Compare(nextPlayUrl, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase) = 0 Then
'' nextPlayUrl = ""
'' End If
'' End If
'' End If
'' End Using
'' Catch ex As Exception
'' LogError(ex, "GetMediaInfo")
'' End Try
''End Sub
Public Function GetContinuousStreamingSampleRate() As Integer
Return If(streamingProfile Is Nothing, 44100, If(streamingProfile.TranscodeSampleRate = -1, If(44100 < streamingProfile.MinimumSampleRate, streamingProfile.MinimumSampleRate, 44100), streamingProfile.TranscodeSampleRate))
End Function
Public Function GetContinuousStreamingBitDepth() As Integer
Return If(streamingProfile Is Nothing, 16, streamingProfile.TranscodeBitDepth)
End Function
Public Function PlayToDevice(url As String, streamHandle As Integer) As Boolean
Try
avTransportStatusTimer.Change(Timeout.Infinite, Timeout.Infinite)
Dim sourceUrl As String
If String.IsNullOrEmpty(url) Then
sourceUrl = "stream"
currentTrackDurationTicks = Long.MaxValue
Else
sourceUrl = url
Long.TryParse(mbApiInterface.Library_GetFileProperty(url, DirectCast(-FilePropertyType.Duration, FilePropertyType)), currentTrackDurationTicks)
End If
Dim metadata As String
Try
Using bufferStream As New IO.MemoryStream, writer As New XmlTextWriter(bufferStream, New UTF8Encoding(False))
url = directory.WriteAudioFileDIDL(writer, PrimaryHostUrl, url, streamHandle)
writer.Flush()
metadata = Encoding.UTF8.GetString(bufferStream.GetBuffer(), 0, CInt(bufferStream.Length))
End Using
Finally
LogInformation("Play", sourceUrl & " (" & url & ")")
End Try
currentPlayStartTimeEstimated = True
playToMode = True
Dim statusCode As String = PostSoapRequest(avTransportControlUrl, "SetAVTransportURI", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID", "CurrentURI", "CurrentURIMetaData"}, New String() {"0", url, metadata}, True)
If statusCode <> "200" Then
LogInformation("Play:SetAVTransportURI", "status=" & statusCode & ",url=" & url)
Return False
End If
statusCode = PostSoapRequest(avTransportControlUrl, "Play", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID", "Speed"}, New String() {"0", "1"}, True)
If statusCode = "200" Then
SyncLock resourceLock
If currentPlayStartTimeEstimated Then
currentPlayStartTicks = DateTime.UtcNow.Ticks
End If
End SyncLock
avTransportStatusTimer.Change(100, statusTimerInterval)
Return True
Else
LogInformation("Play", "status=" & statusCode & ",url=" & url)
End If
Catch ex As Exception
LogError(ex, "Play")
End Try
Return False
End Function
Public Function QueueNext(url As String) As Boolean
If supportSetNextInQueue Then
''Try
'' Dim metadata As String
'' Using bufferStream As New IO.MemoryStream, _
'' writer As New XmlTextWriter(bufferStream, New UTF8Encoding(False))
'' url = directory.WriteAudioFileDIDL(writer, PrimaryHostUrl, url, 0)
'' writer.Flush()
'' metadata = Encoding.UTF8.GetString(bufferStream.GetBuffer(), 0, CInt(bufferStream.Length))
'' End Using
'' Dim statusCode As String = PostSoapRequest(avTransportControlUrl, "SetNextAVTransportURI", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID", "NextURI", "NextURIMetaData"}, New String() {"0", url, metadata}, True)
'' If statusCode <> "200" Then
'' If queueNextFailedCount > 3 Then
'' supportSetNextInQueue = False
'' End If
'' queueNextFailedCount += 1
'' Else
'' queueNextFailedCount = Integer.MinValue
'' ''GetMediaInfo()
'' If String.IsNullOrEmpty(nextPlayUrl) Then
'' supportSetNextInQueue = False
'' Else
'' queueNextTrackPending = True
'' Return True
'' End If
'' End If
''Catch ex As Exception
'' LogError(ex, "QueueNext")
''End Try
End If
Return False
End Function
Public Sub PausePlayback()
If currentPlayState = PlayState.Playing Then
Try
avTransportStatusTimer.Change(Timeout.Infinite, Timeout.Infinite)
currentPlayPositionMs = CInt((DateTime.UtcNow.Ticks - currentPlayStartTicks) \ TimeSpan.TicksPerMillisecond)
Dim statusCode As String = PostSoapRequest(avTransportControlUrl, "Pause", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID"}, New String() {"0"}, True)
If statusCode <> "200" Then
LogInformation("Pause", "status=" & statusCode)
End If
GetPlayStateInformation()
Catch ex As Exception
LogError(ex, "Pause")
End Try
End If
End Sub
Public Sub ResumePlayback()
If currentPlayState = PlayState.Paused Then
Try
Dim statusCode As String = PostSoapRequest(avTransportControlUrl, "Play", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID", "Speed"}, New String() {"0", "1"}, True)
currentPlayStartTicks = DateTime.UtcNow.Ticks - currentPlayPositionMs * TimeSpan.TicksPerMillisecond
If statusCode <> "200" Then
LogInformation("Resume", "status=" & statusCode)
End If
GetPlayStateInformation()
avTransportStatusTimer.Change(0, statusTimerInterval)
Catch ex As Exception
LogError(ex, "Resume")
End Try
End If
End Sub
Public Sub StopPlayback(Optional raiseNotification As Boolean = False)
avTransportStatusTimer.Change(Timeout.Infinite, Timeout.Infinite)
playToMode = False
If currentPlayState <> PlayState.Stopped Then
Try
lastUserInitiatedStop = DateTime.UtcNow.Ticks
queueNextTrackPending = False
PostSoapRequest(avTransportControlUrl, "Stop", "urn:schemas-upnp-org:service:AVTransport:1", New String() {"InstanceID"}, New String() {"0"}, True, True)
currentPlayPositionMs = 0