Черное зеркало: Bandersnatch - это интерактивное приключение по выбору, которое следует из умопомрачительной драмы программиста видеоигр в 1980-х годах. Netflix недавно выпустил этот новый эпизод из сериала «Черное зеркало», посвященного технической антиутопии, после того, как потратил значительное количество времени и энергии на создание новой технологии «отслеживания состояния» для включения интерактивности в игровой процесс.

В этом посте я рассмотрю базовые структуры данных и код, которые Netflix разработал для этого опыта, исследую сетевые вызовы и большие структуры данных JSON, чтобы попытаться понять их безумие.

Выбери свое собственное приключение

Bandersnatch - это шоу о ком-то, кто пытается создать видеоигру с индивидуальным подходом к приключениям, основанную на книге "Выбери свое приключение". Но хитрость с историями по выбору приключений заключается в том, что они не следуют линейным сюжетным линиям, как традиционные повествования, а вместо этого предлагают несколько сюжетных путей с разными концовками, все в зависимости от выбора, который вы делаете на этом пути. Хотя нелинейное повествование было неотъемлемой частью индустрии видеоигр на протяжении десятилетий, его реализация на крупной платформе потокового мультимедиа примечательна.

Во время просмотра Bandersnatch на Netflix зрителю предлагаются варианты выбора, которые появляются на экране. Выбор одного варианта вместо другого изменяет видео, которое вы видите по мере развертывания нового сегмента истории.

Источник (Источник: Netflix)

Ряд прилежных зрителей Reddit попытались наметить варианты, ветвящиеся сюжетные линии и концовки для истории Bandersnatch, пример которой показан ниже в блок-схеме, разработанной пользователем Reddit AppiusClaudius .

Источник (Источник: AppiusClaudius)

Общий эффект от предоставления выбора и обеспечения безупречного видео впечатляет. Так как же Netflix это сделал?

Интерактивные развлечения в Netflix

Bandersnatch - не первая попытка Netflix в области интерактивных медиа, просто она первая, нацеленная на взрослую аудиторию. В прошлом году они впервые представили свою первую попытку в области интерактивных медиа - сериал для детей Кот в сапогах: в ловушке эпической сказки. И, согласно странице справки Netflix, в настоящее время существует 5 заголовков интерактивного контента, и скоро будут добавлены новые.

Но, вероятно, будет справедливо сказать, что Bandersnatch - самая амбициозная интерактивная игра Netflix на сегодняшний день. Настолько, что им пришлось создать новую технологию отслеживания состояния, чтобы обрабатывать миллионы вариантов того, как можно разыграть эту историю, по словам Карлы Энгельбрехт, директора Netflix по инновационным продуктам.

Так что же представляет собой эта технология «отслеживания состояния» и как она работает? Чтобы понять это, я сделал то, что всегда делаю, когда мне интересно, как веб-сайты делают удивительные вещи. Я начал копаться в исходном коде и сетевых вызовах!

Сетевые звонки

Наблюдая за Bandersnatch, я открыл инструменты разработчика Chrome и просто начал отслеживать все сетевые вызовы. В частности, я искал все, что могло иметь отношение к интерактивности или функциям отслеживания состояния. Наблюдая за сетевыми вызовами во время просмотра и делая выбор во время шоу, похоже, что существует около 5 разных URL-адресов запросов, которые регулярно вызываются.

(Источник: Netflix / Джон Энгельсман)

nflxvideo.net

Сложнее всего при доставке контента использовать эти GET-запросы к nflxvideo.net конечной точке, возвращающие обратно application/octet-stream двоичные файлы контента. Они быстро и многократно возвращают мультимедийные данные, чтобы зритель никогда не сталкивался с буферизацией видео.

`https://ipv4-c330-nyc001-ix.1.oca.nflxvideo.net/range/2727743-2752470?o=AQFn7d5Kh9LAp4jb6mieUi1lPH5kaAzEkOl_fiWm5Nwl9KZnnKTMLCgs0wPM4K8SZt3gZHeUzupAIiSM6UEicfursSFWTuhGvv8-ifgf_JxsrhoKVLOv_uF9PyDUsVDtbX7w9tYwR0WXIGRpo4Z3y4clZfW8J3Cu0zFlO2IzhkLUD5ovVgh7r5XMYvCZ9rkucx06MFp0SHOvTy3uk-QcjQ&v=3&e=1546178203&t=qyg5yHh9iczoSr_tvN3prltdmRc&sc=Eq%1F3%25Uc%03k%0Et%7DV%5DRh%0Cf%05Bh7%1F%5Ey%7C%05CY%5B`

По большей части эти вызовы выглядят довольно стандартно для большинства потоков контента Netflix, поэтому они, вероятно, не показывают ничего уникального, что могло бы помочь нам определить интерактивность.

Персонализация

Другой набор сетевых вызовов выглядит немного более многообещающим - вызовы персонализации. Эти запросы POST отправляют полезные двоичные данные в конечную точку /personalization/cl2 в основном www.netflix.com домене.

`https://www.netflix.com/personalization/cl2`

Полезная нагрузка двоичных данных состоит из структуры данных JSON, которая включает много ожидаемой информации для отслеживания (идентификатор пользователя, идентификатор сеанса и т. Д.). Но, похоже, он также включает объект «состояние», который может рассказать нам что-то полезное об интерактивности.

MSL и кадмий:

Есть два сетевых вызова, которые отправляют запросы POST в Netflix API уровня безопасности сообщений (MSL).

События:

`https://www.netflix.com/nq/msl_v1/cadmium/pbo_events/%5E1.0.0/router`

Большие двоичные объекты журнала:

`https://www.netflix.com/nq/msl_v1/cadmium/pbo_logblobs/%5E1.0.0/router`

Этот MSL API используется для «передачи данных между двумя или более взаимодействующими объектами». Похоже, что эти сетевые вызовы отправляют данные журнала / событий, связанные с Cadmium, домашним проигрывателем JavaScript Netflix.

Полезные данные этих вызовов, похоже, включают информацию о токене и подписи пользователя, но неясно, что находится в отправляемых двоичных файлах данных. Однако, учитывая, что аналогичные вызовы регулярно отправляются во время потоковой передачи неинтерактивных медиа, вполне вероятно, что эти вызовы не задействованы в каких-либо интерактивных функциях.

Я должен отметить, что я замалчиваю многие детали в этих сетевых запросах, особенно в отношении заголовков запросов / ответов, полных полезных данных и т. Д. Я просто вытаскиваю достаточно деталей, чтобы попытаться понять суть того, что происходит , но я рекомендую вам изучить некоторые из них самостоятельно.

Шакти

И последнее, но не менее важное в нашем списке сетевых вызовов - это набор POST-запросов к Netflix API с именем Shakti.

`https://www.netflix.com/api/shakti/vdf992f93/pathEvaluator?drmSystem=widevine&isWatchlistEnabled=false&isShortformEnabled=false&isVolatileBillboardsEnabled=false&method=call&falcor_server=0.1.0&withSize=true&materialize=true`

Этот сетевой запрос - тот, который нас здесь интересует, поэтому давайте рассмотрим его более подробно.

Шакти

Судя по небольшой общедоступной информации, кажется, что Shakti API - это еще один сервис для получения данных. Имя Шакти кратко упоминается на слайде 2014 года Райана Анклама, тогдашнего старшего инженера пользовательского интерфейса в Netflix, который обсуждает новые услуги Node.js в компании.

Источник (Фото: Райан Анклам)

Он также упоминается в репозитории github с тем же именем от пользователя HowardStark, однако, похоже, это скорее попытка реверс-инжиниринга извне, а не какое-либо официальное описание Netflix услуга.

Итак, у нас есть некоторое представление о том, что может быть Shakti API, но что еще здесь происходит? Что ж, оглядываясь назад на вызовы Shakti API, похоже, что в URL-адресе запроса есть параметр запроса с именем falcor_server. Поскольку Falcor - это пользовательская библиотека JavaScript Netflix для эффективного извлечения данных, идея о том, что Shakti - это API для извлечения данных, кажется, подтверждается.

Пути вызова Шакти

Есть еще одна важная вещь, которую следует отметить в отношении сетевых вызовов Shakti API. Хотя их URL-адреса выглядят одинаково, существует как минимум 8 различных типов вызовов Shakti API, которые можно различить по своим двоичным файлам. В частности, значениями ключа с именем callPath в полезной нагрузке двоичных данных JSON. Основываясь на поверхностном чтении документации Falcor, эта полезная нагрузка, вероятно, используется Falcor, где значения для callPath направляются к различным внутренним службам.

Я пропущу первые 5 callPaths Шакти, но они перечислены ниже для справки.

  • Рино / Лоломо: "callPath":["reno","newLolomo"]
  • Предварительные матчи: _ 12_
  • Постплей: "callPath":["videos",80988062,"postplay"]
  • Консультации: "callPath":["videos",80988062,"advisories"]
  • Динамические сообщения: "callPath":["dynamicMessages"]

Но подождите, что это? По крайней мере, 3 из 8 callPath, используемых в запросах Shakti, содержат слово «интерактивный».

  • logInteractivePlaybackImpression: "callPath":["logInteractivePlaybackImpression"]
  • logInteractiveStateSnapshots: "callPath":["logInteractiveStateSnapshots"]
  • InteractiveVideoMoments: "callPath":["videos",80988062,"interactiveVideoMoments"]

Это выглядит многообещающе. Мы видим не только слово «интерактивный», но и «состояние». Мы вернемся к двум «журналам» callPath чуть позже, а пока давайте посмотрим на ответ InteractiveVideoMoments callPath.

интерактивныеВидеоМоменты

InteractiveVideoMoments callPath - это 1 из 6 запросов Shakti API, которые вызываются при загрузке страницы, и, похоже, он вызывается только один раз. Поэтому разумно предположить, что это своего рода вызов инициализации.

Но самое заметное в данных ответа для InteractiveVideoMoments callPath - это их большой размер. Действительно большой. Если уточнить структуру JSON, то это более 25 000 строк.

При копировании данных ответа ниже я вырезал большую часть фактического содержания, чтобы показать структуру графика JSON.

{
  "jsonGraph": {
    "videos": {
      "80988062": {
        "interactiveVideoMoments": {
          "$type": "atom",
          "$size": 273073,
          "value": {
            "type": "bandersnatch",
            "choicePointNavigatorMetadata": {
              "config": {
              },
              "storylines": {
              },
              "type": "snapshots",
              "choicePointsMetadata": {
                "timelineLabel": [],
                "choicePoints": {...},
                "choices": null
              }
            },
            "commonMetadata": {
              "layouts": {...},
              "settings": {...}
            },
            "segmentHistory": [],
            "stateHistory": {...},
            "snapshots": [],
            "momentsBySegment": {...},
            "preconditions": {...},
            "audioLocale": "en",
            "segmentGroups": {...}
          }
        }
      }
    }
  },
  "paths": [
    [
      "videos",
      "80988062",
      "interactiveVideoMoments"
    ]
  ]
}

В этом графике JSON, похоже, есть по крайней мере 4 компонента, которые определяют интерактивные компоненты Bandersnatch:

  • stateHistory: инициализация 62 переменных состояния (59 логических и 3 многомерных)
  • momentBySegment: список сегментов видео по типу (сцены, показы, пост-воспроизведения и т. д.), описывающий предварительные условия состояния, новые данные состояния и варианты выбора (также определяемые сегментами).
  • предварительные условия: список определений предварительных условий для каждого сегмента, определяемых простой или сложной условной логикой с использованием переменных состояния.
  • segmentGroups: список идентификаторов групп сегментов и составляющих их сегментов, включая предварительные условия.

Давайте рассмотрим эти компоненты более подробно с точки зрения первого выбора в Bandersnatch, где отец Стефана спрашивает, хочет ли он хлопья Sugar Puffs или Frosties.

Источник (Источник: Netflix)

Государственная история

Начнем со списка stateHistory. На первый взгляд, этот список параметров состояния мало что говорит нам. Но мы знаем, что это своего рода инициализация состояния, и поскольку мы знаем, что Netflix создал новую технологию «отслеживания состояния» для этой интерактивной функции, я предполагаю, что это важный компонент! Мы еще вернемся к этому.

"stateHistory": {
       "p_sp": true,
       "p_tt": true,
       "p_8a": false,
       "p_td": true,
       "p_cs": false,
       "p_w1": false,
       "p_2b": false,
       "p_3j": false,
       "p_pt": false,
       "p_cd": false,
       "p_cj": false,
       "p_sj": false,
       "p_sj2": false,
       "p_tud": false,
       "p_lsd": false,
       "p_vh": false,
       "p_3l": false,
       "p_3s": false,
       "p_3z": false,
       "p_ps": "n",
       "p_wb": false,
       "p_kd": false,
       "p_bo": false,
       "p_5v": false,
       "p_pc": "n",
       "p_sc": false,
       "p_ty": false,
       "p_cm": false,
       "p_pr": false,
       "p_3ad": false,
       "p_s3af": false,
       "p_nf": false,
       "p_np": false,
       "p_ne": false,
       "p_pp": false,
       "p_tp": false,
       "p_bup": false,
       "p_be": false,
       "p_pe": false,
       "p_pae": false,
       "p_te": false,
       "p_snt": false,
       "p_8j": false,
       "p_8d": false,
       "p_8m": false,
       "p_8q": false,
       "p_8s": false,
       "p_8v": false,
       "p_vs": "n",
       "p_scs": false,
       "p_3ab": false,
       "p_3ac": false,
       "p_3aj": false,
       "p_3ah": false,
       "p_3ak": false,
       "p_3al": false,
       "p_3af": false,
       "p_5h": false,
       "p_5ac": false,
       "p_5ag": false,
       "p_5ad": false,
       "p_6c": false
}

Моменты по сегментам

Затем, просматривая список momentBySegment, мы находим сегмент 1A, который кажется связанным с этим первым выбором в шоу, между Sugar Puffs (вариант 1E ) и Frosties (вариант 1D).

"1A": [
  {
    "type": "scene:cs_bs",
    "startMs": 135880,
    "endMs": 153520,
    "activationWindow": [
      135880,
      149520
    ],
    "id": "1A",
    "layoutType": "l2",
    "uiDisplayMS": 139520,
    "uiHideMS": 149520,
    "defaultChoiceIndex": 0,
    "choiceActivationThresholdMS": 135880,
    "choices": [
      {
        "id": "1E",
        "segmentId": "1E",
        "startTimeMs": 153520,
        "text": "SUGAR PUFFS"
      },
      {
        "id": "1D",
        "segmentId": "1D",
        "startTimeMs": 5442480,
        "text": "FROSTIES"
      }
    ],
    "trackingInfo": {
      "viewableId": 80988062
    },
    "impressionData": {
      "type": "userState",
      "data": {
        "persistent": {
          "p_sp": true,
          "p_tt": true,
          "p_8a": false,
          "p_td": true,
          "p_cs": false,
          "p_w1": false,
          "p_2b": false,
          "p_3j": false,
          "p_pt": false,
          "p_cd": false,
          "p_cj": false,
          "p_sj": false,
          "p_sj2": false,
          "p_tud": false,
          "p_lsd": false,
          "p_vh": false,
          "p_3l": false,
          "p_3s": false,
          "p_3z": false,
          "p_ps": "n",
          "p_wb": false,
          "p_kd": false,
          "p_bo": false,
          "p_5v": false,
          "p_pc": "n",
          "p_sc": false,
          "p_ty": false,
          "p_cm": false,
          "p_pr": false,
          "p_3ad": false,
          "p_s3af": false,
          "p_nf": false,
          "p_np": false,
          "p_ne": false,
          "p_pp": false,
          "p_tp": false,
          "p_bup": false,
          "p_be": false,
          "p_pe": false,
          "p_pae": false,
          "p_te": false,
          "p_snt": false,
          "p_8j": false,
          "p_8d": false,
          "p_8m": false,
          "p_8q": false,
          "p_8s": false,
          "p_8v": false,
          "p_vs": "n",
          "p_scs": false,
          "p_3ab": false,
          "p_3ac": false,
          "p_3aj": false,
          "p_3ah": false,
          "p_3ak": false,
          "p_3al": false,
          "p_3af": false,
          "p_5h": false,
          "p_5ac": false,
          "p_5ag": false,
          "p_5ad": false,
          "p_6c": false
        }
      }
    },
    "uiInteractionStartMS": 139520,
    "config": {
      "intervalBasedVideoTimer": true,
      "disableImmediateSceneTransition": true,
      "disablePrematureUserInteraction": true,
      "hideChoiceLabelWhenChoiceHasImage": true,
      "randomInitialDefault": true
    }
  }
]

Здесь есть что распаковать, но есть три вещи, которые стоит выделить. Во-первых, существует несколько определений времени начала и окончания в миллисекундах. Эти значения, кажется, определяют определенные разделы видео, которые относятся как к сегменту, предшествующему выбору, так и к различным сделанным вариантам. Также примечательно, что сегмент 1A имеет только один набор определений типа «сцена: cs_bs ».

Мы также видим, что массив userState из impressionData похож на список параметров stateHistory. Поскольку эти данные кажутся независимыми от того, какой вариант выбран, я предполагаю, что эти данные состояния обновляются еще до того, как выбор будет сделан, возможно, в какой-то момент во время воспроизведения предыдущего фрагмента видео.

Давайте подробнее рассмотрим один из двух вариантов, сегмент 1E (он же Sugar Puffs ):

"1E": [
  {
    "type": "notification:playbackImpression",
    "startMs": 153520,
    "endMs": 207240,
    "precondition": [
      "not",
      [
        "eql",
        [
          "persistentState",
          "p_sp"
        ],
        true
      ]
    ],
    "impressionData": {
      "type": "userState",
      "data": {
        "persistent": {
          "p_sp": true
        }
      }
    }
  },
  {
    "type": "scene:cs_bs",
    "startMs": 190640,
    "endMs": 207240,
    "activationWindow": [
      190640,
      203240
    ],
    "id": "1E",
    "layoutType": "l2",
    "uiDisplayMS": 194280,
    "uiHideMS": 203240,
    "defaultChoiceIndex": 0,
    "choiceActivationThresholdMS": 190640,
    "choices": [
      {
        "id": "1H",
        "segmentId": "1H",
        "startTimeMs": 207240,
        "text": "THOMPSON TWINS"
      },
      {
        "id": "1G",
        "segmentId": "1G",
        "startTimeMs": 5496880,
        "text": "NOW 2"
      }
    ],
    "trackingInfo": {
      "viewableId": 80988062
    },
    "uiInteractionStartMS": 194280,
    "config": {
      "intervalBasedVideoTimer": true,
      "disableImmediateSceneTransition": true,
      "disablePrematureUserInteraction": true,
      "hideChoiceLabelWhenChoiceHasImage": true,
      "randomInitialDefault": true
    }
  }
]

Из сегмента 1A мы знаем, что сегмент 1E - лучший выбор для Sugar Puffs. В отличие от сегмента 1A, мы видим два набора определений: один для типа «сцена: cs_bs» (такой же, как сегмент 1A) и один для типа «уведомление: воспроизведениеImpression». . Определение этого второго типа включает предварительное условие для состояния p_sp и обновление userState того же параметра состояния. Я рискну и скажу, что «sp» в p_sp означает «Sugar Puffs».

Определения для «scene: cs_bs» включают время начала и окончания, а также данные о следующем выборе в конце этого сегмента, в частности, выбор музыкальных лент между Thompson Twins (сегмент 1H) и теперь 2 (сегмент 1G).

Интересно отметить, что данные для сегмента 1D (Frosties) очень похожи на сегмент 1E, включая данные для последующего выбора между сегментами 1H и 1G, с заметной разницей в том, что для параметра состояния p_sp установлено значение false (т.е. не Sugar Puffs).

Мы начинаем видеть здесь небольшую тенденцию, поэтому давайте подведем итоги того, что мы обнаружили на данный момент. Судя по тому, что мы видели, определения сегментов могут включать:

  • Время начала и окончания, связанное с какой-либо видеопоследовательностью
  • Предварительные условия параметров состояния, относящиеся к показам воспроизведения (какими бы они ни были)
  • Обновления параметров состояния
  • Варианты выбора (1 или 2), которые могут произойти в какой-то момент сегмента, и идентификаторы сегментов, связанные с этими вариантами
  • Один или несколько типов определений для каждого сегмента, включая следующие типы: «уведомление: действие», «уведомление: воспроизведениеImpression», «сцена: cs_bs», «сцена: cs_bs_phone» и «сцена: interstitialPostPlay_v2».

Что касается этих типов определения сегментов, похоже, существует два типа уведомлений и три типа сцены. Пока я не буду вдаваться в подробности того, чем могут отличаться эти определения сегментов.

Предварительные условия

Наш следующий тип компонента - это предварительное условие. Давайте немного забегаем вперед в описании Bandersnatch, чтобы изучить этот новый компонент. В какой-то момент Стефан посещает офис доктора Хейнса, и зритель сталкивается с выбором: «Стефан кусает ногти» или «выдергивает мочку уха».

Просматривая список momentBySegment для этого выбора, мы обнаруживаем, что он определен как сегмент 3R. Затем, ища этот идентификатор сегмента в списке предварительных условий, мы находим это определение.

"3R": [
  "not",
  [
    "persistentState",
    "p_vh"
  ]
]

Кажется достаточно простым. Этот сегмент имеет предусловие только для одной переменной состояния, состояния p_vh (vh = Visit Haynes?). Однако неясно, что именно делает это предварительное условие. Это связано с воспроизведением? Или сцена?

Глядя на длинный список предварительных условий, кажется, что они могут варьироваться от простых логических выражений, включающих только одно состояние, до более сложных выражений, включающих множество состояний. Хотя мы не знаем точно, как используются эти предварительные условия, справедливо предположить, что они являются статическими определениями, которые действуют как своего рода контроль потока для структуры ветвящихся сегментов, открывая и закрывая повествовательные пути в зависимости от разные государственные ценности.

Сегментные группы

Давайте на секунду остановимся на сегменте 3R и посмотрим на наш последний набор компонентов в нашем графике JSON, которые кажутся важными, сегментные группы. Этот компонент кажется способом организовать структуру соединения сегментов. Некоторые сегментные группы имеют статический набор сегментов, составляющих группу, тогда как другие имеют динамические определения, основанные на предварительных условиях.

Например, сегмент 3R отображается в двух разных группах сегментов, VisitHaynesChoice и 3Q. Группа VisitHaynesChoice представляет собой набор из 6 различных идентификаторов сегментов.

"VisitHaynesChoice": [
  "ZP",
  "ZQ",
  "3Xa",
  "3Xac",
  "3S",
  "3R"
]

А сегментная группа 3Q представляет собой набор из двух идентификаторов сегментов, причем один из них (3S), по-видимому, включается в группу только на основе критерий предварительного условия, помеченный 3S_s3Q.

"3Q": [
  {
    "segment": "3S",
    "precondition": "3S_s3Q"
  },
  "3R"
]

Глядя на другие группы сегментов, мы видим одни статически определенные, например VisitHaynesChoice, другие динамически определенные с предварительными условиями, например 3Q, и даже некоторые, которые включают другие группы внутри segmentGroup.

Если этого недостаточно, следует отметить, что имена идентификаторов могут существовать либо как определенные сегменты, сегментные группы, предварительные условия или моментыBySegments. , и может существовать только как один из этих элементов или иметь один и тот же идентификатор для нескольких элементов. Таким образом, в то время как идентификатор 3S_s3Q является просто предварительным условием, идентификатор 3Q является одновременно сегментом и сегментной группой.

Отображение всего этого

Составить карту полного потока всех этих различных сегментов, групп, предварительных условий и состояний - непростая задача. Способ попытаться понять все это - показать, как логика и структуры данных, указанные выше, связаны с конкретными сегментами видео.

К счастью, сообщение Reddit от пользователя iamthecage не только показывает нам, что на самом деле эпизод представляет собой единое мастер-видео продолжительностью более 5 часов, но также нашел способ программно переходить к различным частям видео. Сообщение включает в себя некоторый код JavaScript, который можно ввести в консоль разработчика, что позволяет вручную переходить к определенному времени в видео (в миллисекундах). Я скопировал этот фрагмент кода ниже для удобства, если вы хотите попробовать его самостоятельно.

document.evaluate('//*[@id="80988062"]/video',document).iterateNext().setAttribute("controls", "controls")

netflix.appContext.getPlayerApp().getAPI().getOpenPlaybackSessions()[0].currentTime

const videoPlayer = netflix
  .appContext
  .state
  .playerApp
  .getAPI()
  .videoPlayer

// Getting player id
const playerSessionId = videoPlayer
  .getAllPlayerSessionIds()[0]

const player = videoPlayer
  .getVideoPlayerBySessionId(playerSessionId)
  
player.seek(1577120)

(Источник: iamthecage)

Используя наш пример сегмента 3R сверху, мы можем отобразить некоторые из связанных с ним видеоклипов, используя время начала и окончания, детализированное как в choicePoints, так и в momentBySegment компоненты (показаны на изображении ниже).

(Источник: Джон Энгельсман)

Здесь показано, как определены эти компоненты данных (в формате JSON) и как они соотносятся с конкретными точками основного видео для этого конкретного сегмента 3R. Посмотрев на это с другой стороны, мы можем показать, как параметры состояния и предварительные условия запускают различные комбинации типов видеосегментов (сцены, показы воспроизведения и т. Д.) И связанные с ними варианты.

(Источник: Джон Энгельсман)

Отсюда мы можем начать видеть, как эти компоненты работают вместе при составлении схемы сюжетного потока. И как они используются для перехода к различным сегментам основного видео в зависимости от того, какой выбор сделан и от общего состояния интерактивной истории, что каким-то образом приводит к плавному воспроизведению мультимедиа.

Хорошо, это кажется сложным, правда?

Оглядываясь назад на некоторые из блок-схем, которые я видел на Reddit, я должен задаться вопросом, не упускают ли они какие-то повествовательные пути, особенно потому, что они, кажется, содержат элементарное описание всех 62 параметров состояния. Количество элементов в каждом из основных компонентов, которые мы исследовали, показывает, насколько сложной и большой структурой Bandersnatch кажется скрытый вид:

  • Предварительные условия: 241
  • momentBySegment: 208
  • segmentGroups: 111
  • stateHistory: 62

При таком большом количестве сегментов и параметров состояния существует вероятность большого разнообразия повествования в зависимости от сложности предварительных условий для различных сегментов видео.

Мы знаем, что для того, чтобы справиться с этой сложностью, Netflix пришлось создать новое программное обеспечение под названием Branch Manager, чтобы построить нелинейное повествование. Шоураннер Black Mirror Чарли Букер описывает сложность составления карты Bandersnatch, используя только блок-схему:

Вы не можете сделать это с помощью блок-схемы, потому что она динамическая и отслеживает, в каком состоянии вы находитесь, и делаете что-то соответствующим образом », - пояснил Брукер, который мог« ввести и доставить свой развивающийся сценарий прямо в Netflix »с помощью изящного инструмента.

Еще один интересный аспект всего этого заключается в том, что Netflix каким-то образом удалось реализовать эти интерактивные компоненты в контексте своей существующей потоковой инфраструктуры. Они взяли видео продолжительностью более 5 часов, построили поверх него сложный слой состояния / предварительных условий / сегментации, а затем разработали процесс перехода назад и вперед к различным точкам основного видео без какой-либо буферизации видео. Тот факт, что они смогли сделать все это, полагаясь на те же сервисы Shakti API, которые они используют для другого контента Netflix, действительно говорит о надежности и универсальности сервисов, которые они создают.

Исходный код и Акира

Итак, мы увидели, как Netflix использовал свой Shakti API для доставки данных инициализации, которые определяют весь интерактивный рассказ и его структуру видео. Но как все это работает вместе, чтобы на самом деле управлять просматриваемым видео и обеспечить удобство работы с мультимедиа? Разработали ли они что-то новое, чтобы справиться с аспектом интерактивности сегментации видео? Чтобы разобраться в этом, нам нужно проверить некоторый исходный код на стороне клиента.

Просматривая теги скриптов на главной HTML-странице заголовка Bandersnatch, я наткнулся на интересную библиотеку JavaScript под названием Akira. В неминифицированном виде это более 89 000 строк кода. Приведенный ниже URL-адрес запроса загружает эту клиентскую библиотеку:

https://codex.nflxext.com/%5E2.0.0/truthBundle/webui/0.0.1-akira-js-mk-v6faf08f4/js/js/akira%7CakiraClient.js/2/4u4A494b4k06424f4z040n004B4e474i4h4c4p4s4n444g4y114v/l/true/none

Мне не удалось найти в Интернете упоминания об этой конкретной библиотеке Netflix, поэтому я не уверен, что это что-то новое или что-то, что Netflix уже давно использует. Но, глядя на другой эпизод Black Mirror (неинтерактивный эпизод), мы видим, что аналогичная библиотека Akira загружается через URL-адрес запроса ниже:

https://codex.nflxext.com/%5E2.0.0/truthBundle/webui/0.0.1-akira-js-mk-v6faf08f4/js/js/akira%7CakiraClient.js/2/4u4l4A494b4k06424f4z040n004B4e474h4c4p4s4n444g114v/l/true/none

Хотя URL-адреса выглядят одинаково, мы видим длинную строку символов (может быть, хеш?), Которые выглядят немного по-разному в каждом из двух запросов.

Interactive?: 4u4A494b4k06424f4z040n004B4e474i4h4c4p4s4n444g4y114v
Non-Interactive?: 4u4l4A494b4k06424f4z040n004B4e474h4c4p4s4n444g114v

Удаление неинтерактивной библиотеки Akira показывает чуть менее 83 000 строк кода, что примерно на 6000 строк меньше, чем в интерактивной версии. Беглое сравнение этих библиотек показывает, что интерактивная библиотека Akira имеет ссылки на четыре изученных нами интерактивных компонента (предварительные условия, stateHistory, momentBySegment и segmentGroups), а неинтерактивная библиотека Akira - нет.

Таким образом, похоже, что эта библиотека Akira обрабатывает большую часть (или все) клиентские функции технологии «отслеживания состояния» Netflix, теоретически обновляя значения состояния, оценивая предварительные условия и переходя между сегментами видео, все на основе компонентов данных, загруженных с помощью API Шакти.

Хотя неясно, создал ли Netflix библиотеку Акиры специально для Bandersnatch, эпизод содержит тонкий (если не анахроничный) намек на одноименную хитовую мангу Кацухиро Отомо 1982 года. Во время визита Стефана в квартиру Колина на заднем плане вырисовывается большой черно-белый снимок, показывающий разрушение Нео-Токио. Я хотел бы думать, что это ссылка на библиотеку JavaScript, которая, кажется, сделала возможной Bandersnatch.

Источник (Источник: Netflix)

ОБНОВЛЕНИЕ: Оказывается, Netflix уже давно использует клиент Akira! Я нашел статью о взаимодействии Netflix в 2015 году, где тогдашний директор по инженерным операциям Джош Эванс дает следующий комментарий:

«Мы создали то, что мы называем нашим« дарвиновским »пользовательским интерфейсом, перемещая вертикальные кадры в горизонтальные, и настроили наши алгоритмы… было внесено много инноваций», - говорит Эванс.

Такой же интерфейс присутствует и на веб-сайте, создавая то, что Эванс называет пользовательским интерфейсом «Акира».

«Вся необходимая информация у вас под рукой», - говорит он. Звучит просто, но в его основе - расширенная телеметрия, аналитика в реальном времени и передовое машинное обучение.

Таким образом, хотя кажется, что Netflix уже некоторое время использует пользовательский интерфейс Akira, я все же считаю важным отметить, что они обслуживают две разные версии клиентской библиотеки, в зависимости от того, является ли заголовок интерактивным или нет.

В Зазеркалье

Понятно, что Netflix приложил много усилий для разработки этой интерактивной технологии для Bandersnatch. Для этого они, кажется, создали повествовательный JSON Graph, более 6000 строк нового клиентского кода и даже новый инструмент Branch Manager, чтобы разложить сложную повествовательную структуру эпизода.

Эта статья - попытка осмыслить часть этой работы, исследовать и осмыслить детали, которые вошли в новую потоковую технологию, лежащую в основе Bandersnatch. Будет интересно посмотреть, какие новые интерактивные игры Netflix может разработать дальше, и будут ли они продолжать развивать технологии и концепции, рассмотренные в этом посте.

Спасибо за чтение!

Джон Энгельсман

Первоначально опубликовано на сайте engelsjk.com 30 декабря 2018 г.