Pull to refresh

Музыка для бабушки от внука-программиста

Reading time 8 min
Views 33K
Моя бабушка с юности привыкла слушать радио, но, к сожалению, сейчас по радио можно услышать далеко не лучшие композиции и передачи. Радио можно заменить любимой музыкой, но, увы, у моей бабушки сложности в привыкании к технике.
Лучший подарок – подарок, сделанный своими руками, поэтому я, как любящий внук и программист под мобильные платформы, принялся думать, как обеспечить бабушке простой доступ к её любимой музыке.

Сразу скажу, что успеха я достиг, а вот тем, кому интересен процесс – прошу под кат.

main

Задачи


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

Выбор железа


Необходимо было выбрать три вещи: аудио-колонку, подставку для смартфона и сам смартфон.
Здесь, забегая вперёд, скажу, что выбор мой весьма спорный: можно было приобрести другие модели с другими характеристиками и ценой. Но как получилось, так получилось.
Изначально хотелось, чтобы смартфон был связан с колонкой по Bluetooth, т.к. чем меньше проводов будет у бабушки, тем лучше. Поэтому была выбрана колонка Nokia MD-50W: она достаточно громкая, имеет встроенный аккумулятор, стандартный micro-USB разъем зарядки, Bluetooth и даже NFC.

Смартфон было решено выбирать на платформе Windows Phone, т.к. во-первых, под эту платформу у меня больше опыта разработки, чем под Android, а во-вторых, интерфейс системы вполне прост для восприятия человеком, который вообще не имеет никакого опыта в работе с интерфейсами.
Разработка велась, когда только-только вышло обновление GDR2 для Windows Phone 8.0, в которой добавили поддержку радио. Нужно было еще найти аппарат с microSD-картой и ярким немаленьким экраном. Как раз после недели поисков в России запустилась модель Nokia Lumia 625, которая на тот момент единственная имела GDR2, а также самый большой экран по диагонали (4.7”).

Итак, смартфон едет из Москвы, колонка – из Америки, осталось выбрать, какая док-станция будет добираться ко мне из Китая.
Я забыл упомянуть, что во всем приходилось выдерживать цветовой стиль: колонка и смартфон покупались исключительно белого цвета, т.к. я учитывал интерьер, в котором эта конструкция собиралась стоять. Оказалось, что белых док-станций, с разъемом microUSB очень мало. Видимо, потому, что в Китае считается, раз белое – значит для Apple, раз черное – значит всё остальное. Еще захотелось иметь вторую выемку на док-станции для того, чтобы можно было ставить смартфон горизонтально для просмотра видео (мало ли). Такая док-станция вообще была одна. Решено было заказать две: с доп. выемкой и без.

Увы, Китай опечалил. Разъем в док-станции с доп. выемкой располагался ровно под прямым углом к поверхности, что было очень неудобно. Зато в док-станции без выемки разъем располагался как надо — под небольшим углом. Решено было просто склеить док-станции вместе – получилась забавная лесенка, которая, кстати, более устойчива при нажатии на смартфон, поскольку тяжелее в два раза.

док-станция

У любого Windows Phone есть одна ненужная деталь – кнопка поиска. Если поставить регион USA, а поиск по умолчанию Bing, то да – запускается более-менее толковое приложение, но нам в данном контексте эта кнопка вообще не нужна, поэтому просто заклеиваем её, чтобы не нажать ненароком. Идею подсмотрел у сотрудников Яндекса, даже наклейка одноимённая.

кнопки

Прикручиваем радио


Тут вскрылся очередной просчёт. Поскольку радио в смартфоне использует наушники как антенну, то при беспроводном подключении к колонке радио просто не включается. Позже получилось заставить его работать через код, но оно заработало исключительно через встроенный динамик. Пришлось подключать колонку к смартфону кабелем.
В Windows Phone 8.0 почему-то сразу не включили API для работы с радио, поэтому он работает только в обновлении GDR2 и выше (при наличии аппаратной поддержки радио, конечно).

С радио оказалось работать довольно просто:

FMRadio radioInstance = FMRadio.Instance;
// Выставляем регион 
radioInstance.CurrentRegion = RadioRegion.Europe;
// Включаем радио
radioInstance.PowerMode = RadioPowerMode.On;
// Выставляем частоту
radioInstance.Frequency = frequency;

Frequency – это и есть частота радиостанции, которую нам надо менять. Во встроенной программе «Радио» есть режим поиска радиостанции. К сожалению, поиски по успешной его реализации ни к чему не привели.
Ничего страшного: замеряем частоты, которые принимаются в том месте, где будет стоять смартфон и просто прописываем хардкодом. Затем создаем кнопки и на каждую навешиваем по радиостанции. Кнопки делаем большими, чтобы можно было особо не целиться.
Добавляем кнопку «Назад» в самом верху, чтобы не заморачиваться с аппаратными кнопками: в Lumia 625 аппаратные кнопки не подсвечиваются, а на экране кнопку не увидеть будет сложно.

радио

Решаем проблему с выключением экрана


Итак, радио работает. Есть опасения, что даже такой простой жест разблокировки, как свайп вверх, бабушке будет затруднителен. Значит надо минимизировать его использование, отключив блокировку экрана.
В разных прошивках Windows Phone встречается разный набор режимов времени для отключения экрана. Nokia Lumia 920 (не AT&T) – вообще единственная Nokia на WP 8.0, которая имеет опцию «Отключать дисплей через: Никогда». В остальных аппаратах этот пункт выпилен и стоит максимум 5 минут.

Для сравнения: слева Nokia 920, справа 1020 (тоже самое на 520, 620, 625, 720, 820 и 920 AT&T).

920 1020

Но начиная с GDR2 есть же навигационные программы, которые не выключают дисплей – значит как-то это можно сделать в коде. Ок, шерстим MSDN и находим:

PhoneApplicationService phoneAppService = PhoneApplicationService.Current;
phoneAppService.UserIdleDetectionMode = IdleDetectionMode.Disabled;

Эти строчки держат дисплей включенным, если только пользователь сам не нажал кнопку блокировки. Это то, что нужно.

Дружим приложение с картой памяти


Теперь нам нужно реализовать работу с картой памяти. Хочется, чтобы на экране появлялись кнопки с названиями папок с музыкой также, как в режиме радио кнопки с радиостанциями.
Но есть проблема: Windows Phone умеет работать с музыкой, но только если музыка зашита в самом приложении (прощай карта памяти и быстрая замена контента), либо если музыка обязательно лежит в папке Music (прощай работа с папками).
Костыль нашелся не быстро.
Сначала в манифесте WMAppManifest.xml сообщаем системе, что мы будем распознавать только свой формат файлов на карте памяти, а остальные файлы мы замечать не будем. Причем формат будет содержать mp3-контент. Пусть формат будет называться, например, «mp3d» (можно назвать как угодно, кроме общеизвестных расширений).

 <Extensions>
      <FileTypeAssociation Name="mp3" NavUriFragment="fileToken=%s" TaskID="_default">
        <SupportedFileTypes>
          <FileType ContentType="application/mp3">.mp3d</FileType>
        </SupportedFileTypes>
      </FileTypeAssociation>
    </Extensions>

Минус в том, что нам придется переименовать все mp3-файлы в mp3d-файлы, но команда ren сделает всё за нас.

ren *.mp3 *mp3d

Теперь, пишем метод, который цепляется к карточке и формирует список подпапок моей папки с музыкой.

private async Task<List<ExternalStorageFolder>> ReadFoldersFromSdAsync()
{
	List<ExternalStorageFolder> folders = new List<ExternalStorageFolder>();
    // Запрашиваем SD-карту
    ExternalStorageDevice _sdCard = (await ExternalStorage.GetExternalStorageDevicesAsync()).FirstOrDefault();

    // Если карта есть
    if (_sdCard != null)
    {
		// Запрашиваем папку MyMusic
        ExternalStorageFolder routesFolder = await _sdCard.GetFolderAsync("MyMusic");
        // Запрашиваем все подпапки в этой папке
        IEnumerable<ExternalStorageFolder> routeFolders = await routesFolder.GetFoldersAsync();
        folders = routeFolders.ToList();
    }
    return folders;
}

Используем этот метод, создавая кнопки и добавляя их в список (стили опущу – они есть в исходниках). В Tag кладу текущую папку, чтобы потом до нее достучаться по клику (OnTap).

foreach (var f in folders)
{
	IEnumerable<ExternalStorageFile> files = await f.GetFilesAsync();
    if (files.Count() > 0)
    {
		Button button = new Button
        {
			Tag = f,
            Height = 85,
            Width = 456,
            Content = f.Name
        };
        button.Tap += this.OnTap;
        ContentStack.Children.Add(button);
    }
} 

Внутри OnTap-ивента шаманим: копируем mp3d-файл внутрь приложения в виде mp3-файла myFile.mp3, берем URI скопированного файла и скармливаем MediaElement’у в качестве ресурса:

IEnumerable<ExternalStorageFile> files = await folder.GetFilesAsync();

// Перемешиваем файлы
...
// Берем первый файл с муз.композицией
ExternalStorageFile file = files.First();
Uri uri = null;
// Считываем из файла
using (Stream streamToRead = await file.OpenForReadAsync())
{
	if (streamToRead.Length <= int.MaxValue)
    {
		int length = (int)streamToRead.Length;
        Byte[] bytes = new Byte[length];
        streamToRead.Read(bytes, 0, length);

		// Запрашиваем локальное хранилище приложения и создаем файл
        StorageFolder local = Windows.Storage.ApplicationData.Current.LocalFolder;
        var fileToWrite = await local.CreateFileAsync("myFile.mp3", CreationCollisionOption.ReplaceExisting);
        // Записываем в локальный файл нашу композицию
        using (Stream streamToWrite = await fileToWrite.OpenStreamForWriteAsync())
        {
			streamToWrite.Write(bytes, 0, length);
        }
        // Формируем URI на локальный файл
        uri = new Uri(fileToWrite.Path, UriKind.Absolute);
    }
}
if (uri != null)
{
	MusicElement.Source = uri;
}

Музыка играет, движемся дальше.

Пишем время на заставке


Было решено сделать так, чтобы по истечении какого-то времени бездействия (не нажато ни одной кнопки) на экране появлялись крупно часы, число и день недели.
Для того, чтобы появлялось время, заводим таймер с интервалом в одну секунду:

DispatcherTimer dtm = new DispatcherTimer();
dtm.Interval = TimeSpan.FromSeconds(1);
dtm.Tick += OnTimerTick;
dtm.Start();

По ивенту считаем количество тиков и если оно равно 29, то показываем время на экране:

private void OnTimerTick(Object s, EventArgs e)
{
    UpdateDate();
    _ticks++;
    if (_ticks == 29)
    {
		// Показываем время
	}
}

При нажатии на любую другую кнопку сбрасываем _ticks в ноль.
Помимо этого, таймер выполняет функцию обновления времени раз в секунду: мы вызываем функцию, которая парсит DateTime.Now и распределяет по текстовым полям.

Добавляем слайд-шоу


Смотреть просто на время скучно, поэтому сделаем подложку со слайд-шоу из фотографий. Где можно достать много фотографий одного размера и хорошего с эстетической точки зрения содержания? С сайта Bing!
Фотографии одного размера мне понадобились из-за того, что их очень удобно подставлять в одну и ту же анимацию: ничего не собьется и не поплывет не туда.
На торрентах лежат архивы, но там фотографии разных размеров – не пойдет. Есть отличный сервис, позволяющий получить картинки Bing, но картинки только под экран смартфона (я хочу больше), и только за последнюю неделю.
Наконец, нашел архив, удовлетворяющий моим требованиям. Осталось только выкачать.
Картинки были сложены в папку Pictures на сам телефон.
Приложению было сказано брать эти картинки по одной в случайном порядке:

bg.Source = PictureDecoder.DecodeJpeg(_mediaLibrary.Pictures[_random.Next(_imageQuantity)].GetImage());

Затем рисуем анимацию, в которой объект Image перемещается горизонтально, а после меняет картинку на следующую.

заставка

Рисуем значки


Интерфейс Windows Phone оказался весьма кстати, поскольку получилось вынести на рабочий стол огромные ярлыки на приложения, по которым уж точно не промахнешься. Поскольку мы делаем элементарный интерфейс, то значками выступили просто слова «Радио» и «Музыка».

иконки

На рабочий стол вынес также два ярлыка на SPB TV: Первый канал и Культура.

Пишем инструкцию


Как бы я ни старался сделать интерфейс простым, я не мог предугадать, как с ним подружится моя бабушка, поэтому написал инструкцию со скриншотами и фотографиями. Инструкцию опубликую вместе с исходниками – вдруг кому пригодится для бабушки.

Итоги


Самое главное моё опасение не оправдалось: не прошло и двух минут, как я начал объяснять бабушке, как пользоваться приложением, а она уже сама начала нажимать на кнопки и ярлыки, ставить песни, закрывать и открывать приложения. Блокировку восприняла на отлично, сказала, что это как будто «шторку поднять или опустить». Так что цели были успешно достигнуты – теперь у нее звучит любимая музыка. Подарок своими руками удался!

готово

Исходники и инструкция

UPD:
По просьбам трудящихся добавляю фото в интерьере:
Tags:
Hubs:
+91
Comments 14
Comments Comments 14

Articles