@takeoffmedia/react-native-penthera 0.2.64 → 0.2.65
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/android/src/main/java/com/takeoffmediareactnativepenthera/virtuoso/OfflineVideoEngine.kt +64 -52
- package/ios/Penthera.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/Penthera.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/ios/Penthera.xcodeproj/project.xcworkspace/xcuserdata/joseguerreroot.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/Penthera.xcodeproj/xcuserdata/joseguerreroot.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +104 -0
- package/ios/Penthera.xcodeproj/xcuserdata/joseguerreroot.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
- package/package.json +1 -1
package/android/src/main/java/com/takeoffmediareactnativepenthera/virtuoso/OfflineVideoEngine.kt
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
package com.takeoffmediareactnativepenthera.virtuoso
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.app.Activity
|
|
5
5
|
import android.content.Context
|
|
@@ -7,7 +7,7 @@ import android.util.Log
|
|
|
7
7
|
import com.bitmovin.player.api.media.subtitle.SubtitleTrack
|
|
8
8
|
import com.bitmovin.player.reactnative.PlayerModule
|
|
9
9
|
import com.bitmovin.player.reactnative.converter.JsonConverter
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
12
12
|
import com.google.gson.Gson
|
|
13
13
|
import com.penthera.common.Common.AuthenticationStatus
|
|
@@ -29,17 +29,20 @@ import com.facebook.react.uimanager.UIManagerModule
|
|
|
29
29
|
import com.facebook.react.bridge.Promise
|
|
30
30
|
import com.facebook.react.bridge.ReadableArray
|
|
31
31
|
import com.penthera.virtuososdk.client.AncillaryFile
|
|
32
|
+
import okhttp3.OkHttpClient
|
|
33
|
+
import okhttp3.Request
|
|
32
34
|
import org.json.JSONArray
|
|
33
35
|
import org.json.JSONObject
|
|
36
|
+
import java.io.IOException
|
|
34
37
|
import java.net.HttpURLConnection
|
|
35
38
|
import java.text.SimpleDateFormat
|
|
36
39
|
import java.util.Calendar
|
|
37
|
-
|
|
40
|
+
|
|
38
41
|
class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
39
|
-
|
|
42
|
+
|
|
40
43
|
lateinit var virtuoso : Virtuoso
|
|
41
44
|
private lateinit var queueObserver: AssetQueueObserver
|
|
42
|
-
|
|
45
|
+
|
|
43
46
|
var asset: IAsset? = null
|
|
44
47
|
var assetId: MutableList<String> = mutableListOf()
|
|
45
48
|
private val gson = Gson()
|
|
@@ -48,32 +51,33 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
48
51
|
var valueBackPlaneUrl: String = ""
|
|
49
52
|
var valuePublicKey: String = ""
|
|
50
53
|
var valuePrivateKey: String = ""
|
|
51
|
-
|
|
54
|
+
private val client = OkHttpClient()
|
|
55
|
+
|
|
52
56
|
private val enginePauseObserver: Observers.IEngineObserver = object : EngineObserver() {
|
|
53
57
|
override fun engineStatusChanged(status: Int) {
|
|
54
58
|
Log.e("MiModulo", "<<<<<<<<<<status $status>>>>>>>>>>>>")
|
|
55
59
|
}
|
|
56
60
|
}
|
|
57
|
-
|
|
61
|
+
|
|
58
62
|
private fun getInstanceVirtuoso(): Virtuoso {
|
|
59
63
|
virtuoso = Virtuoso(context)
|
|
60
64
|
queueObserver = AssetQueueObserver(this, virtuoso)
|
|
61
65
|
onResume()
|
|
62
66
|
return virtuoso
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
|
|
65
69
|
fun onResume() {
|
|
66
70
|
virtuoso.onResume()
|
|
67
71
|
virtuoso.addObserver(queueObserver)
|
|
68
72
|
virtuoso.addObserver(enginePauseObserver)
|
|
69
73
|
}
|
|
70
|
-
|
|
74
|
+
|
|
71
75
|
fun onPause() {
|
|
72
76
|
virtuoso.onPause()
|
|
73
77
|
virtuoso.removeObserver(queueObserver)
|
|
74
78
|
virtuoso.removeObserver(enginePauseObserver)
|
|
75
79
|
}
|
|
76
|
-
|
|
80
|
+
|
|
77
81
|
fun setup(
|
|
78
82
|
userId: String = valueUserId,
|
|
79
83
|
backplaneUrl: String = valueBackPlaneUrl,
|
|
@@ -86,9 +90,9 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
86
90
|
valueBackPlaneUrl = backplaneUrl
|
|
87
91
|
valuePrivateKey = privateKey
|
|
88
92
|
valuePublicKey = publicKey
|
|
89
|
-
|
|
93
|
+
|
|
90
94
|
val nameDevice = android.os.Build.MODEL
|
|
91
|
-
|
|
95
|
+
|
|
92
96
|
vir.startup(
|
|
93
97
|
URL(valueBackPlaneUrl),
|
|
94
98
|
valueUserId,
|
|
@@ -106,9 +110,9 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
106
110
|
).let {
|
|
107
111
|
promise?.resolve("Penthera initialization successfully")
|
|
108
112
|
}
|
|
109
|
-
|
|
113
|
+
|
|
110
114
|
}
|
|
111
|
-
|
|
115
|
+
|
|
112
116
|
fun downloadAsset(newItem: String) {
|
|
113
117
|
setup()
|
|
114
118
|
val data = JSONObject(newItem)
|
|
@@ -136,7 +140,7 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
136
140
|
URL(url)
|
|
137
141
|
)
|
|
138
142
|
)
|
|
139
|
-
|
|
143
|
+
|
|
140
144
|
val subtitles = JSONArray(item["subtitles"] as String)
|
|
141
145
|
for (i in 0 until subtitles.length()) {
|
|
142
146
|
val subtitle = subtitles.getJSONObject(i)
|
|
@@ -152,7 +156,7 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
152
156
|
)
|
|
153
157
|
)
|
|
154
158
|
}
|
|
155
|
-
|
|
159
|
+
|
|
156
160
|
val params = MPDAssetBuilder().apply {
|
|
157
161
|
assetId(item["id"].toString())
|
|
158
162
|
manifestUrl(URL(url))
|
|
@@ -163,10 +167,10 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
163
167
|
setClientSideAdSupport(true)
|
|
164
168
|
withAncillaryFiles(fileList)
|
|
165
169
|
}.build()
|
|
166
|
-
|
|
170
|
+
|
|
167
171
|
virtuoso.assetManager.createMPDSegmentedAssetAsync(params)
|
|
168
172
|
}
|
|
169
|
-
|
|
173
|
+
|
|
170
174
|
fun getDownloads(): String? {
|
|
171
175
|
val completedList = mutableListOf<MutableMap<String, Any>>()
|
|
172
176
|
val cursor = virtuoso.assetManager?.cursor
|
|
@@ -176,7 +180,7 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
176
180
|
return gson.toJson(completedList)
|
|
177
181
|
}
|
|
178
182
|
val columnNames = cursor?.columnNames
|
|
179
|
-
|
|
183
|
+
|
|
180
184
|
while (cursor?.moveToNext() == true && columnNames != null) {
|
|
181
185
|
val dataMap = mutableMapOf<String, Any>()
|
|
182
186
|
var eap = ""
|
|
@@ -225,29 +229,29 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
225
229
|
put("effectiveExpiryDate", if (value == "0") eadFormated else eapFormated)
|
|
226
230
|
}
|
|
227
231
|
}
|
|
228
|
-
|
|
232
|
+
|
|
229
233
|
}
|
|
230
234
|
}
|
|
231
235
|
dataMap["isPaused"] = false
|
|
232
236
|
completedList.add(dataMap)
|
|
233
237
|
}
|
|
234
238
|
cursor?.close()
|
|
235
|
-
|
|
239
|
+
|
|
236
240
|
return gson.toJson(completedList)
|
|
237
241
|
}
|
|
238
|
-
|
|
242
|
+
|
|
239
243
|
private fun dateToString(seconds: String): String {
|
|
240
244
|
val secondsLong = seconds.toLong()
|
|
241
245
|
val calendar = Calendar.getInstance()
|
|
242
246
|
calendar.add(Calendar.SECOND, secondsLong.toInt())
|
|
243
|
-
|
|
247
|
+
|
|
244
248
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
|
|
245
249
|
return dateFormat.format(calendar.time)
|
|
246
250
|
}
|
|
247
|
-
|
|
251
|
+
|
|
248
252
|
class AssetParseObserver(activity: Context) : ISegmentedAssetFromParserObserver {
|
|
249
253
|
private val mActivity: Context = activity
|
|
250
|
-
|
|
254
|
+
|
|
251
255
|
@SuppressLint("ShowToast")
|
|
252
256
|
override fun complete(asset: ISegmentedAsset?, error: Int, addedToQueue: Boolean) {
|
|
253
257
|
// Show a process when the asset to parsed and added to queue
|
|
@@ -262,17 +266,17 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
262
266
|
// }
|
|
263
267
|
}
|
|
264
268
|
}
|
|
265
|
-
|
|
266
|
-
|
|
269
|
+
|
|
270
|
+
|
|
267
271
|
fun deleteMany(assetIds: ReadableArray, promise: Promise) {
|
|
268
272
|
val assetManager = virtuoso.assetManager
|
|
269
273
|
val result = Arguments.createMap()
|
|
270
|
-
|
|
274
|
+
|
|
271
275
|
for (i in 0 until assetIds.size()) {
|
|
272
276
|
val idValue = assetIds.getString(i)
|
|
273
277
|
val asset = assetManager.getByAssetId(idValue)
|
|
274
278
|
.firstOrNull() as? IAsset
|
|
275
|
-
|
|
279
|
+
|
|
276
280
|
if (asset != null) {
|
|
277
281
|
assetManager.delete(asset)
|
|
278
282
|
result.putBoolean(idValue, true)
|
|
@@ -282,26 +286,34 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
282
286
|
assetId.remove(idValue)
|
|
283
287
|
}
|
|
284
288
|
}
|
|
285
|
-
|
|
289
|
+
|
|
286
290
|
promise.resolve(result);
|
|
287
291
|
}
|
|
288
|
-
|
|
289
|
-
|
|
292
|
+
|
|
293
|
+
|
|
290
294
|
fun getByAssetId(assetId: String): String? {
|
|
291
295
|
//load asset if it has already been downloaded
|
|
292
|
-
val list: MutableList<IIdentifier>? =
|
|
293
|
-
|
|
296
|
+
val list: MutableList<IIdentifier>? = getInstanceVirtuoso().assetManager.getByAssetId(assetId)
|
|
297
|
+
|
|
294
298
|
list?.let {
|
|
295
299
|
if (it.isNotEmpty()) {
|
|
296
300
|
asset = list[0] as VirtuosoSegmentedFile
|
|
301
|
+
|
|
302
|
+
// This a workaound to works ancillary files
|
|
297
303
|
val offlineUrl = asset?.playbackURL
|
|
298
|
-
|
|
304
|
+
val request = Request.Builder()
|
|
305
|
+
.url(offlineUrl.toString())
|
|
306
|
+
.build()
|
|
307
|
+
client.newCall(request).execute().use { response ->
|
|
308
|
+
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
|
309
|
+
}
|
|
310
|
+
|
|
299
311
|
val keyValueMap = HashMap<String, Any>()
|
|
300
312
|
keyValueMap["offlineUrl"] = offlineUrl.toString()
|
|
301
313
|
keyValueMap["metadata"] = asset?.metadata.toString()
|
|
302
|
-
|
|
314
|
+
|
|
303
315
|
val ancillaryFiles = (asset as ISegmentedAsset).getAncillaryFiles(context)
|
|
304
|
-
|
|
316
|
+
|
|
305
317
|
//HERE THE ASSET MANIFEST IS REQUESTED
|
|
306
318
|
keyValueMap["ancillary"] = ancillaryFiles
|
|
307
319
|
return gson.toJson(keyValueMap)
|
|
@@ -309,33 +321,33 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
309
321
|
}
|
|
310
322
|
return null
|
|
311
323
|
}
|
|
312
|
-
|
|
324
|
+
|
|
313
325
|
fun updateUI() {}
|
|
314
|
-
|
|
326
|
+
|
|
315
327
|
fun loadBitmovinSourceManager(assetId: String, nativeId: String, startOffset: Double?, ancillaries: String , promise: Promise) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
328
|
+
|
|
329
|
+
getInstanceVirtuoso().assetManager?.getByAssetId(assetId)?.firstOrNull()?.let { asset ->
|
|
330
|
+
|
|
319
331
|
val sourceManager = BitmovinSourceManager(context, asset as ISegmentedAsset)
|
|
320
332
|
val sourceItem = sourceManager.bitmovinSourceItem
|
|
321
333
|
val playerModule = context.getNativeModule(PlayerModule::class.java)
|
|
322
334
|
if (playerModule != null && sourceItem != null) {
|
|
323
|
-
|
|
335
|
+
|
|
324
336
|
var player = playerModule.getPlayer(nativeId)
|
|
325
337
|
while(player == null) {
|
|
326
338
|
Thread.sleep(100)
|
|
327
339
|
player = playerModule.getPlayer(nativeId) as Nothing?
|
|
328
340
|
}
|
|
329
|
-
|
|
341
|
+
|
|
330
342
|
if (player != null) {
|
|
331
343
|
val metadata = JSONObject(asset.metadata)
|
|
332
344
|
val subtitles = JSONArray(metadata["subtitles"] as String)
|
|
333
345
|
val ancillaryFiles = JSONArray(ancillaries)
|
|
334
346
|
for (i in 0 until subtitles.length()) {
|
|
335
347
|
val subtitle = subtitles.getJSONObject(i)
|
|
336
|
-
|
|
348
|
+
|
|
337
349
|
var url: String = ""
|
|
338
|
-
|
|
350
|
+
|
|
339
351
|
for (j in 0 until ancillaryFiles.length()) {
|
|
340
352
|
val ancillary = ancillaryFiles.getJSONObject(j)
|
|
341
353
|
if(ancillary.getString("description") == subtitle.getString("language")) {
|
|
@@ -346,13 +358,13 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
346
358
|
val label: String = subtitle.getString("label")
|
|
347
359
|
val format = "vtt"
|
|
348
360
|
sourceItem.addSubtitleTrack(SubtitleTrack(url, language, label, format))
|
|
349
|
-
|
|
361
|
+
|
|
350
362
|
}
|
|
351
|
-
|
|
363
|
+
|
|
352
364
|
if(startOffset != null) {
|
|
353
365
|
sourceItem.options.startOffset = startOffset
|
|
354
366
|
}
|
|
355
|
-
|
|
367
|
+
|
|
356
368
|
player.load(sourceItem)
|
|
357
369
|
promise.resolve(true)
|
|
358
370
|
}
|
|
@@ -360,9 +372,9 @@ class OfflineVideoEngine(private val context: ReactApplicationContext) {
|
|
|
360
372
|
}
|
|
361
373
|
promise.reject("Error", "Error loading asset")
|
|
362
374
|
}
|
|
363
|
-
|
|
375
|
+
|
|
364
376
|
private fun uiManager(): UIManagerModule? =
|
|
365
377
|
context.getNativeModule(UIManagerModule::class.java)
|
|
366
|
-
|
|
367
|
-
|
|
378
|
+
|
|
379
|
+
|
|
368
380
|
}
|
|
Binary file
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<Bucket
|
|
3
|
+
uuid = "3AE98629-7421-4561-B12C-C06B8B6A3023"
|
|
4
|
+
type = "1"
|
|
5
|
+
version = "2.0">
|
|
6
|
+
<Breakpoints>
|
|
7
|
+
<BreakpointProxy
|
|
8
|
+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
9
|
+
<BreakpointContent
|
|
10
|
+
uuid = "A5D166B0-3C57-4F4D-A845-BDB81498DA0F"
|
|
11
|
+
shouldBeEnabled = "No"
|
|
12
|
+
ignoreCount = "0"
|
|
13
|
+
continueAfterRunningActions = "No"
|
|
14
|
+
filePath = "Penthera.swift"
|
|
15
|
+
startingColumnNumber = "9223372036854775807"
|
|
16
|
+
endingColumnNumber = "9223372036854775807"
|
|
17
|
+
startingLineNumber = "18"
|
|
18
|
+
endingLineNumber = "18"
|
|
19
|
+
landmarkName = "Penthera"
|
|
20
|
+
landmarkType = "3">
|
|
21
|
+
</BreakpointContent>
|
|
22
|
+
</BreakpointProxy>
|
|
23
|
+
<BreakpointProxy
|
|
24
|
+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
25
|
+
<BreakpointContent
|
|
26
|
+
uuid = "5D7AE47B-D5FC-4468-A75B-C414F3F8A58C"
|
|
27
|
+
shouldBeEnabled = "No"
|
|
28
|
+
ignoreCount = "0"
|
|
29
|
+
continueAfterRunningActions = "No"
|
|
30
|
+
filePath = "Penthera.swift"
|
|
31
|
+
startingColumnNumber = "9223372036854775807"
|
|
32
|
+
endingColumnNumber = "9223372036854775807"
|
|
33
|
+
startingLineNumber = "25"
|
|
34
|
+
endingLineNumber = "25"
|
|
35
|
+
landmarkName = "updateStatusInfo()"
|
|
36
|
+
landmarkType = "7">
|
|
37
|
+
</BreakpointContent>
|
|
38
|
+
</BreakpointProxy>
|
|
39
|
+
<BreakpointProxy
|
|
40
|
+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
41
|
+
<BreakpointContent
|
|
42
|
+
uuid = "E3C50B30-46CF-4D56-8C49-C257C70E0ACC"
|
|
43
|
+
shouldBeEnabled = "No"
|
|
44
|
+
ignoreCount = "0"
|
|
45
|
+
continueAfterRunningActions = "No"
|
|
46
|
+
filePath = "Penthera.swift"
|
|
47
|
+
startingColumnNumber = "9223372036854775807"
|
|
48
|
+
endingColumnNumber = "9223372036854775807"
|
|
49
|
+
startingLineNumber = "26"
|
|
50
|
+
endingLineNumber = "26"
|
|
51
|
+
landmarkName = "updateStatusInfo()"
|
|
52
|
+
landmarkType = "7">
|
|
53
|
+
</BreakpointContent>
|
|
54
|
+
</BreakpointProxy>
|
|
55
|
+
<BreakpointProxy
|
|
56
|
+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
57
|
+
<BreakpointContent
|
|
58
|
+
uuid = "75EF43BF-56FF-4727-B372-E1A0EBC02C61"
|
|
59
|
+
shouldBeEnabled = "Yes"
|
|
60
|
+
ignoreCount = "0"
|
|
61
|
+
continueAfterRunningActions = "No"
|
|
62
|
+
filePath = "Penthera.swift"
|
|
63
|
+
startingColumnNumber = "9223372036854775807"
|
|
64
|
+
endingColumnNumber = "9223372036854775807"
|
|
65
|
+
startingLineNumber = "101"
|
|
66
|
+
endingLineNumber = "101"
|
|
67
|
+
landmarkName = "initializeSdk(user:backplaneUrl:publicKey:privateKey:resolve:reject:)"
|
|
68
|
+
landmarkType = "7">
|
|
69
|
+
</BreakpointContent>
|
|
70
|
+
</BreakpointProxy>
|
|
71
|
+
<BreakpointProxy
|
|
72
|
+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
73
|
+
<BreakpointContent
|
|
74
|
+
uuid = "EF36B51B-4FC2-4989-A791-180E3212EFE3"
|
|
75
|
+
shouldBeEnabled = "Yes"
|
|
76
|
+
ignoreCount = "0"
|
|
77
|
+
continueAfterRunningActions = "No"
|
|
78
|
+
filePath = "Penthera.swift"
|
|
79
|
+
startingColumnNumber = "9223372036854775807"
|
|
80
|
+
endingColumnNumber = "9223372036854775807"
|
|
81
|
+
startingLineNumber = "149"
|
|
82
|
+
endingLineNumber = "149"
|
|
83
|
+
landmarkName = "getDownloadedAsset(assetID:)"
|
|
84
|
+
landmarkType = "7">
|
|
85
|
+
</BreakpointContent>
|
|
86
|
+
</BreakpointProxy>
|
|
87
|
+
<BreakpointProxy
|
|
88
|
+
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
|
89
|
+
<BreakpointContent
|
|
90
|
+
uuid = "C467C00E-2428-4D24-843A-E7CDF28CB553"
|
|
91
|
+
shouldBeEnabled = "Yes"
|
|
92
|
+
ignoreCount = "0"
|
|
93
|
+
continueAfterRunningActions = "No"
|
|
94
|
+
filePath = "Penthera.swift"
|
|
95
|
+
startingColumnNumber = "9223372036854775807"
|
|
96
|
+
endingColumnNumber = "9223372036854775807"
|
|
97
|
+
startingLineNumber = "148"
|
|
98
|
+
endingLineNumber = "148"
|
|
99
|
+
landmarkName = "getDownloadedAsset(assetID:)"
|
|
100
|
+
landmarkType = "7">
|
|
101
|
+
</BreakpointContent>
|
|
102
|
+
</BreakpointProxy>
|
|
103
|
+
</Breakpoints>
|
|
104
|
+
</Bucket>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>SchemeUserState</key>
|
|
6
|
+
<dict>
|
|
7
|
+
<key>Penthera.xcscheme_^#shared#^_</key>
|
|
8
|
+
<dict>
|
|
9
|
+
<key>orderHint</key>
|
|
10
|
+
<integer>0</integer>
|
|
11
|
+
</dict>
|
|
12
|
+
</dict>
|
|
13
|
+
</dict>
|
|
14
|
+
</plist>
|