@pbvision/partner-sdk 0.1.18 → 0.1.20

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.
Files changed (3) hide show
  1. package/README.md +24 -1
  2. package/package.json +1 -1
  3. package/src/index.js +68 -6
package/README.md CHANGED
@@ -19,6 +19,7 @@ standard length game, so we'll notify your servers when the results are ready.
19
19
  - [Auto-tagging players](#auto-tagging-players)
20
20
  - [Folders](#folders)
21
21
  - [Recording with the PB Vision app](#recording-with-the-pb-vision-app)
22
+ - [Reporting a game's score](#reporting-a-games-score)
22
23
  - [Video Editors and Viewers](#video-editors-and-viewers)
23
24
  - [After Video Processing is Done](#after-video-processing-is-done)
24
25
  - [Callback Data](#callback-data)
@@ -168,6 +169,7 @@ entirely, or provide some or all of these fields:
168
169
  | `facility` | `string` | Name of the facility where the game was recorded, e.g. `"Cool Club #3 - Barcelona"`. Useful for facility and Court Insight integrations. |
169
170
  | `court` | `string` | Court identifier where the game was recorded, e.g. `"11A"`. Useful for facility and Court Insight integrations. |
170
171
  | `fid` | `integer` | Folder ID, e.g. from `getOrCreateFolder()` (see [Folders](#folders)). Puts the video in that folder in your partner account's library. A `fid` that isn't one of your folders is refused with a 400 error (`unknown folder`). |
172
+ | `playersFolderPath` | `string[]` | Where the video goes in the PB Vision library of everyone it's shared with: the folder names from the top of their library down, e.g. `['Mon Oct 19 open play']`. 1 to 5 names, each 1 to 200 characters with no leading or trailing whitespace. Folders they don't have are made for them. |
171
173
  | `playersForTagging` | `object` | The players to **auto-tag** once processing completes, each by a name, an email, or both (see [Auto-tagging players](#auto-tagging-players)). Honored by all of `uploadVideo()`, `sendVideoUrlToDownload()`, `makeVideoId()`, and `makeRecordingLink()`. |
172
174
 
173
175
  The `facility` and `court` fields are primarily used by facility partners
@@ -218,7 +220,8 @@ games automatically.
218
220
 
219
221
  Folders organize the videos in your partner account's library. To put a new
220
222
  video in a folder, pass the folder's ID as the `fid` [metadata](#video-metadata)
221
- field.
223
+ field. To file it in the libraries of the people it's shared with, pass
224
+ `playersFolderPath`.
222
225
 
223
226
  `getOrCreateFolder()` returns the folder with the name you give (inside
224
227
  `parent`, if you pass one), creating it if needed, so it's safe to retry. Use a
@@ -342,6 +345,26 @@ shows a page for installing it.
342
345
  **Testing.** Links from the test server (`useProdServer: false`) start with
343
346
  `pbvision://record` and need a development build of the PB Vision app.
344
347
 
348
+ ### Reporting a game's score
349
+
350
+ If you know a game's final score, report it with `setGameScore()` and PB
351
+ Vision shows it with the game. Name each team by its role at the game's first
352
+ serve, as in [`playersForTagging`](#auto-tagging-players): the team that served
353
+ the first point, and the team that received it. Give the video by its `vid` or
354
+ by the `nonce` you made it with.
355
+
356
+ ```javascript
357
+ await pbv.setGameScore({ nonce: `${eventId}-${gameId}`, server: 11, receiver: 7 });
358
+ ```
359
+
360
+ You can report before the video is processed, or even recorded: the game gets
361
+ the score when processing finishes, and the response's `applied` says whether
362
+ it has it yet. Report again to correct a score; the latest report wins. A
363
+ score someone enters in PB Vision itself stands, though: a report never
364
+ replaces it, and `applied` is false. Each team's result is its points, or
365
+ `'won'` or `'lost'` if that is all you know. The score is for a video of one
366
+ game.
367
+
345
368
  ### Video Editors and Viewers
346
369
 
347
370
  Editors can tag themselves and friends in the video. Viewers have read-only
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pbvision/partner-sdk",
3
3
  "description": "SDK for interacting with the PB Vision pickleball AI engine.",
4
- "version": "0.1.18",
4
+ "version": "0.1.20",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "engines": {
package/src/index.js CHANGED
@@ -139,13 +139,14 @@ export class PBVision {
139
139
  * @param {VideoMetadata} [metadata]
140
140
  * @returns {VideoUrlToDownloadResponse}
141
141
  */
142
- async sendVideoUrlToDownload (videoUrl, { userEmails = [], name, desc, gameStartEpoch, facility, court, fid, playersForTagging } = {}) {
142
+ async sendVideoUrlToDownload (videoUrl, { userEmails = [], name, desc, gameStartEpoch, facility, court, fid, playersFolderPath, playersForTagging } = {}) {
143
143
  assert(typeof videoUrl === 'string' && videoUrl.startsWith('http'),
144
144
  'URL must be a string beginning with http')
145
145
  assert(videoUrl.split('?')[0].endsWith('.mp4'), 'video URL must have the .mp4 extension')
146
+ assertFolderPath(playersFolderPath)
146
147
  const resp = await this.__callAPI(
147
148
  'add_video_by_url',
148
- { url: videoUrl, userEmails, name, desc, gameStartEpoch, facility, court, fid, playersForTagging })
149
+ { url: videoUrl, userEmails, name, desc, gameStartEpoch, facility, court, fid, playersFolderPath, playersForTagging })
149
150
  return JSON.parse(resp)
150
151
  }
151
152
 
@@ -182,6 +183,12 @@ export class PBVision {
182
183
  * @property {integer} [fid] the ID of the folder in your partner account's
183
184
  * library to add this video to (see getOrCreateFolder()). A fid that is
184
185
  * not one of your folders is refused.
186
+ * @property {Array<string>} [playersFolderPath] where this video goes in
187
+ * the PB Vision library of everyone it's shared with: the folder names
188
+ * from the top of their library down, e.g. ['Mon Oct 19 open play'] or
189
+ * ['Open play', 'Oct 19']. Folders they don't have are made for them.
190
+ * 1 to 5 names, each 1 to 200 characters with no leading or trailing
191
+ * whitespace.
185
192
  * @property {PlayersForTagging} [playersForTagging] the players to tag
186
193
  * automatically once processing completes, each by a name, an email, or
187
194
  * both. Identify players by their role at the start of the first game.
@@ -238,9 +245,10 @@ export class PBVision {
238
245
  * no `vid`, when the account paying for the video can't pay for it; for
239
246
  * passthrough partners it is always present.
240
247
  */
241
- async makeVideoId ({ fileExt = 'mp4', userEmails = [], name, desc, gameStartEpoch, facility, court, fid, playersForTagging, nonce } = {}) {
248
+ async makeVideoId ({ fileExt = 'mp4', userEmails = [], name, desc, gameStartEpoch, facility, court, fid, playersFolderPath, playersForTagging, nonce } = {}) {
249
+ assertFolderPath(playersFolderPath)
242
250
  const resp = await this.__callAPI('make_video_id',
243
- { platform: getPlatform(), fileExt, userEmails, name, desc, gameStartEpoch, facility, court, fid, playersForTagging, nonce })
251
+ { platform: getPlatform(), fileExt, userEmails, name, desc, gameStartEpoch, facility, court, fid, playersFolderPath, playersForTagging, nonce })
244
252
  const { hasCredits, vid } = JSON.parse(resp)
245
253
  const ret = { vid, uid: this.uid }
246
254
  if (hasCredits !== undefined) {
@@ -262,6 +270,8 @@ export class PBVision {
262
270
  * @property {string} [court] the court where the game is played
263
271
  * @property {integer} [fid] the ID of the folder in your partner account's
264
272
  * library to add the video to (see getOrCreateFolder())
273
+ * @property {Array<string>} [playersFolderPath] where the video goes in
274
+ * the library of everyone it's shared with (see VideoMetadata)
265
275
  * @property {PlayersForTagging} [playersForTagging] the players to tag
266
276
  * automatically once processing completes
267
277
  * @property {string} [nonce] identifies the game, e.g. your own ID for it:
@@ -320,7 +330,7 @@ export class PBVision {
320
330
  */
321
331
  async makeRecordingLink ({
322
332
  resolution = '1080p', fps = 30, mayChangeResolution = false, mayChangeFPS = false,
323
- gameStartEpoch, userEmails, name, desc, facility, court, fid, playersForTagging, nonce
333
+ gameStartEpoch, userEmails, name, desc, facility, court, fid, playersFolderPath, playersForTagging, nonce
324
334
  } = {}) {
325
335
  assert(gameStartEpoch === undefined,
326
336
  'makeRecordingLink() does not take gameStartEpoch: the game\'s time comes from its recording, and a time set now would override it')
@@ -329,6 +339,7 @@ export class PBVision {
329
339
  assert(RECORDING_FPS.includes(fps), `fps must be 30 or 60, not ${JSON.stringify(fps)}`)
330
340
  assert(typeof mayChangeResolution === 'boolean', 'mayChangeResolution must be a boolean')
331
341
  assert(typeof mayChangeFPS === 'boolean', 'mayChangeFPS must be a boolean')
342
+ assertFolderPath(playersFolderPath)
332
343
 
333
344
  // checked before making the video, so a 4K link that PB Vision wouldn't
334
345
  // analyze never gets one; only a yes is remembered, so an account that
@@ -338,7 +349,7 @@ export class PBVision {
338
349
  }
339
350
 
340
351
  const { vid, uid, hasCredits } = await this.makeVideoId({
341
- fileExt: 'mp4', userEmails, name, desc, facility, court, fid, playersForTagging, nonce })
352
+ fileExt: 'mp4', userEmails, name, desc, facility, court, fid, playersFolderPath, playersForTagging, nonce })
342
353
  if (hasCredits === false) {
343
354
  throw new Error('PB Vision did not make a video for this recording link: the account paying for it has no credits available (hasCredits is false)')
344
355
  }
@@ -359,6 +370,51 @@ export class PBVision {
359
370
  return { vid, uid, url: `${this.recordUrl}?${query}` }
360
371
  }
361
372
 
373
+ /**
374
+ * One team's result in a game: the points it scored, `'won'` or `'lost'`
375
+ * when that is all you know, or `null` when you don't know it.
376
+ *
377
+ * @typedef {number|'won'|'lost'|null} TeamResult
378
+ */
379
+
380
+ /**
381
+ * @typedef {Object} SetGameScoreResponse
382
+ * @property {string} vid the video
383
+ * @property {boolean} applied whether the game has the score now. While
384
+ * the video awaits processing, false, and processing gives the game the
385
+ * score when it finishes. After, false means the game keeps the score it
386
+ * has: someone entered it in PB Vision, or PB Vision couldn't tell which
387
+ * team served first
388
+ */
389
+
390
+ /**
391
+ * Reports the final score of a game you recorded, for a video of one game
392
+ * that your partner account owns. Name each team by its role at the game's
393
+ * first serve, as playersForTagging names the players: the team that
394
+ * served the first point, and the team that received it. You can report
395
+ * before the video is processed, or even recorded. Report again to correct
396
+ * it: the latest report wins, though never over a score someone entered in
397
+ * PB Vision itself.
398
+ *
399
+ * @param {Object} score
400
+ * @param {string} [score.vid] the video; give this or nonce
401
+ * @param {string} [score.nonce] the nonce you made the video with; give
402
+ * this or vid
403
+ * @param {TeamResult} score.server the result of the team that served the
404
+ * game's first point
405
+ * @param {TeamResult} score.receiver the result of the team that received
406
+ * it
407
+ * @returns {SetGameScoreResponse}
408
+ */
409
+ async setGameScore ({ vid, nonce, server, receiver }) {
410
+ assert((vid === undefined) !== (nonce === undefined), 'give either vid or nonce')
411
+ const isResult = x => x === null || x === 'won' || x === 'lost' || (Number.isInteger(x) && x >= 0)
412
+ assert(isResult(server) && isResult(receiver),
413
+ 'each team\'s result must be its points, \'won\', \'lost\' or null')
414
+ const resp = await this.__callAPI('video/score/set', { vid, nonce, server, receiver })
415
+ return JSON.parse(resp)
416
+ }
417
+
362
418
  /**
363
419
  * Upload a video for processing by the AI.
364
420
  *
@@ -604,6 +660,12 @@ async function startResumableUpload (bucket, objName, numBytesTotal) {
604
660
  }
605
661
  }
606
662
 
663
+ // playersFolderPath is a list of folder names, not a single name
664
+ function assertFolderPath (playersFolderPath) {
665
+ assert(playersFolderPath === undefined || Array.isArray(playersFolderPath),
666
+ 'playersFolderPath must be an array of folder names, e.g. [\'Mon Oct 19 open play\']')
667
+ }
668
+
607
669
  // GCS asks clients to retry these, with exponential backoff
608
670
  function isTemporaryStatus (status) {
609
671
  return status >= 500 || status === 408 || status === 429