Почему Digital Audio Book сейчас не умеет проигрывать файлы с карты памяти

Начну пожалуй издалека, а точнее с устройства системы в плане многозадачности. Когда звучат возгласы что Windows Phone не многозадачная система, то они немного не правы, WP8 построена на ядре NT от “большой” Windows и естественно она является многозадачной, но абсолютно все в мобильной операционной системе завязано на одну вещь – батарею. Использует программа процессор – расходуется батарея, использует много памяти – расходуется батарея, использует доступ к памяти для хранения файлов – используется батарея, любой чих, любой программы расходует батарею. Поэтому разработчики системы наложили кучу ограничений, которые распространяются на все программы и мы как разработчики должны их соблюдать, например: ни одна программа не может использовать более 100 Мб памяти иначе приходит специальный процесс и пришибает тебя, ни одно приложение не имеет права работать в фоне, даже если приложение накрыло локскрином (который тоже программа), то все, считается, что ты в фоне, из этого правила есть исключения, но об этом позже. Жизненный цикл приложений WP тоже отдельная интересная тема, сначала тебя помещают в фон, в так называемое состояние dormand, где останавливаются таймеры, отвязываются события, но ты все еще в памяти, если система примет решение (например ей нужно больше ресурсов, которые ты просто занимаешь и ничего не делаешь), то тебя переведут состояние tombstone (дословно “надгробная плита”, я бы назвал это “овощ”), ты все еще в памяти, но у тебя сбросят ресурсы и в конечном итоге система может решить, что ты ей в памяти вообще не сдался и тебя просто пришибут. 

Как упомянул выше есть способ изменить это поведение, определенными флагами приложения можно сказать системе “я буду работать в фоне, меня не трогайте”, тут становится все на совести разработчика, в фоне не желательно задействовать акселерометр, выбрасывать много событий, короче не “шуметь” в системе, естественно повышается риск не пройти сертификацию в магазин. Как я понял этот механизм является таким “костылем” который ввели на ранних стадиях системы, когда поняли, что гайки сильно закрутили, а нормальных человеческих механизмов выполнения простейших задач не дали, например воспроизведение музыки. В ранних версиях Windows Phone вообще не было способа воспроизводить музыку под локскрином, этот функционал появился только в 7.5, поэтому первые проигрыватели (системный не беру, он вообще не от мира сего) все поголовно работали в фоне. У этого механизма, работы в фоне есть один неприятный момент, активировав его один раз в программе, выключить его нельзя пока программа не будет перезагружена. Именно его использует таймер сна в Digital Audio Book, по другому заставить работать таймер в фоне мне не удалось, при активации таймера вам выводится куча сообщений и предупреждений. Именно по этой причине таймер не активируется в режиме энергосбережения, т.к. в режиме сохранения энергии системе с большей долей вероятности может прийти в голову прийти и убить процесс. И именно из-за этого странного поведения механизма включения работы в фоне, мне предпочтительней что-бы таймер именно закрывал приложение, а не просто останавливал воспроизведение, поэтому я каждый раз и надоедаю, что перезагрузите приложение, перезагрузите приложение, после того как отработает таймер сна.

Вернемся к нашим баранам, в WP есть такое понятие, как Scheduled Tasks, так называемые фоновые запланированные задачи, которые могут выполнять какие-либо работы в фоне, им не важно активно основное приложение или спит или вообще выключено, они бывают двух видов PeriodicTask и ResourceIntensiveTask, первый для легких задач, например проверить новую почту, обновить тайл погоды, второй более “тяжелый”, пересоздать базу данных, переконвертировать все фотографии, для каждого из них тоже куча ограничений и оговорок, подробно останавливаться не буду, всем желающим в документацию. Называются эти фоновые задачи агентами, в WP7.5 появилась еще одна разновидность фоновых агентов BackgroundAudioPlayer Agent, который собственно и позволяет воспроизводить музыку в фоне.

Если коротко, то воспроизведение музыки в WP ведет свое начало от проигрывателя Zune (это как iPod только от Microsoft) от него нам в наследство осталось только одноименное десктопное приложение и куча библиотек внутри WP, но самое главное это концепция UVC (Universal Volume Control). Пробовали когда-нибудь запустить два проигрывателя на устройстве? Один останавливает воспроизведение другого или просто падает если не умеет этого делать, поток звука в устройстве один и регулятор громкости кстати тоже. Так вот в 7.5 запилили фоновый агент который может управлять и принимать события этого самого UVC, с технической точки зрения для меня это выглядит как отдельный проект с .dll на который имеет ссылку основное приложение. Когда в недрах телефона происходит какое-нибудь событие связанное с моей музыкой, выбрасывается событие, на которое подписан мой код фонового агента и у меня есть аж 30 секунд и 20 мегабайт памяти (в документации написано 15, но системный счетчик говорит что все-же 20) на обработку этого события. Например когда заканчивается одна глава книги, мне выбрасывается событие TrackEnded, я тогда иду в свою базу данных ищу запись о книге, которая сейчас отмечена, что текущая, выясняю какая глава играла, ищу следующую, ссылку на нее отдаю фоновому проигрывателю и говорю “играй”. Когда пользователь нажимает паузу на гарнитуре, это тоже отслеживает фоновый проигрыватель, выбрасывает событие, что пользователь что-то там нажал, я разбираюсь чего он там нажал, ага пауза, даю команду фоновому проигрывателю, тот выбрасывает второе событие изменения состояния… Все хорошо все прекрасно, но есть одно жирное “НО”, BackgroundAudioPlayer умеет играть файлы только из локального хранилища программы, в документации прям так английским по белому и написано:

The BackgroundAudioPlayer can only play files from isolated storage or from a remote URI.

Т.е. только файлы лежащие “под ногами” у программы, т.к. программы нельзя ставить на SD карту, или с удаленного адреса. Встает закономерный вопрос, а если получить ссылки на файлы лежащие на SD кате и отдать их фоновому проигрывателю в виде этих самых remote URL.

Вот тут мы подходим к вопросу, как работать с SD картой. Ну для начала в документации сказано, что с карты можно только читать

Direct access to the SD card is read-only and is limited to file types for which your app has registered to handle with a file association. “прямой доступ к карте памяти только для чтения, ограничен типами файлами, для которых вы пропишите ассоциации в своем приложении”

А нам собственно большего и не нужно, нужно только прописать ассоциации для типов нужных файлов в своем приложении и нам дадут доступ к файлам, можно будет притаскивать их к себе “под ноги" и их уже скармливать фоновому проигрывателю. Но и тут появляется пара пресловутых “Но”, не все расширения можно зарезервировать для работы с ними, есть системные, которые зарезервированы самой операционкой, а есть просто типы файлов которые зарезервированы встроенными приложениями и туда входят все поддерживаемые типы изображений, видео и звука. Перечень зарезервированных расширений системой. Т.е. зарегистрировать к примеру расширение .mp3 мне не дадут, а вот .fb2 можно, чем собственно программы чтения простых книг и пользуются. Тут еще дело осложняется вот чем, можно например зарегистрировать расширение .dabmp3 и заставлять пользователя переименовывать файлы перед заливкой на карту памяти, когда идет обращение за таким файлом система не дает имени папки, она дает токен этого файла, по которому можно запросить потом поток, т.к. для чтения мета информации, которая лежит в IDv3 тегах я использую библиотеку TagLib, а она сейчас для определения типа файла опирается как раз на расширение. Придется ее здорово дорабатывать, а я ее и так уже порядком “покромсал” что-бы заставить работать на телефоне. Так вот что-бы определить какие файлы логически относятся к какой книге нужно их будет перетаскать к себе в локальную память и даже если это будет сделано, к примеру дополнительным файлом метаописания (тем же .dab, который я использую при загрузке со SkyDrive) при воспроизведении придется таскать файлы с флэшки в локальную память, это будет приводить к паузе между главами, иногда очень заметной, бывают книги состоящие всего из 10-20 больших файлов, тогда писать менеджер который будет это делать заранее, но тут опять проблема, пользователь может захотеть “прыгруть” в середину книги… Книги-же в формате .M4B могут и вовсе состоять из одного-двух файлов в 400 Мб, а это залипание при переходе между файлами более минуты и абсолютно никакой экономии места в локальном хранилище. В итоге, на обычные расширения подписаться не дадут, а если изменить расширения они играть не будут, без переноса в локальную память, хотя нужно проверить. Тут дело осложняется тем, что эмулятор не поддреживает эмуляцию SD карт, а физическое устройство у меня HTC8X, у которого карт памяти тоже нет.

На самом деле библиотек для воспроизведения в системе две, есть еще старая в пространстве имен Microsoft.Xna.Framework.Media вот именно она заточена для работы с медиабиблиотекой. Да забыл уточнить, медиабиблиотека – это логическая библиотека, ее файлы могут располагаться как во внутренней памяти устройства, так на SD карте, но для системы она видна как одна логическая. Мысль была следующая, перевести телефон в работу в фоновом режиме, тот который упоминал выше и спокойно вызывать ее методы. Но, что главное для аудиокниг: первое — порядок воспроизведения, но вроде как у класса Song есть свойство TrackNumber и второе, самое главное возможность запомнить последнюю позицию. Считать текущее положение скажем нам дают у класса MediaPlayer есть свойство PlayPosition, но засада в том, что это свойство только на чтение, восстановить или перемотать трек до нужной позиции в библиотеке возможности нет. А BackgroundAudioPlayer, у которого есть методы перехода на нужную точку воспроизведения, не видит этого воспроизведения когда музыку или главу книги играет эта библиотека. Вот такой замкнутый круг. Та библиотека может но не видит файлы медиабиблиотеки, эта видит, но не хватает функционала.

Если вдруг кого, как и меня, посетит шальная мысль засунуть объекты работы с медиабиблиотекой в фоновый агент, т.е. нажали кнопку “Играй”, тот в фоне выбросил событие, мы создаем медиабиблиотеку, начинаем воспроизведение, забудьте статья Unsupported APIs for background agents for Windows Phone документации гласит:

On Windows Phone 8, all APIs in all XNA Framework namespaces are unsupported except the following:

 

Для Windows Phone 8 все методы из пространства XNA запрещено к использованию, за исключением методов для работы с фотографиями, да и один фиг метода установки позиции нет.

Так куда нам бедным податься и как решать эту проблему? С выходом WP8 Microsoft начала ослаблять возжи, о чем говорит хоть появление библиотеки MediaLibraryExtensions, которая начала позволять сохранять файлы музыки из локальной памяти в медиабиблиотке, при опеределенных настройках, можно считать, что на карту памяти. А вот читать оттуда пока не разрешили, зато там есть методы чтения изображений, надеюсь, что в следующих релизах она расширится, как раз методами чтения для файлов звука.

В разделе для разработчиков, где можно высказать свое “фи” и дать предложения об улучшению платформы есть заявка:

Integrate BackgroundAudioPlayer and XNA MediaLibrary

Allow the BackgroundAudioPlayer to play tracks from the MediaLibrary. Or allow applications that implement the XNA MediaPlayer class to run in the background.

 

Которая как раз целиком и полностью относится ко всему тому что я тут понаписал. За нее проголосовало уже 187 разработчиков, даже есть официальный ответ представителя команды разработчиков:

Cliff Simpkins (Sr Product Manager, Windows Phone Developer) responded  ·  Nov 1, 2012

Thank you for the suggestion. We are not adding this capability in the 8.0 release, but know that it is high on the consideration list for future Windows Phone releases.

Keep those suggestions and votes coming!

Типа, да, знаем проблема есть, в 8.0 тупо не успели допилить, только вот когда допилят вопрос остается открытым. Сейчас с нетерпением жду изменений в GDR2 (обновление системы такое), которое должно начать раздаваться в начале-середине июля, но чет терзают смутные сомнения, что в этом плане раньше выхода 8.1 ничего нового не будет. Хотя поживем, увидим.