/lib/Teto/DAAP.pm

http://github.com/motemen/Teto · Perl · 132 lines · 94 code · 33 blank · 5 comment · 4 complexity · af1e8cf0fb1174822f523c8b987fb7eb MD5 · raw file

  1. package Teto::DAAP;
  2. use Mouse;
  3. use Teto::Playlist;
  4. use Teto::Track;
  5. use AnyEvent::DAAP::Server;
  6. use Sys::Hostname;
  7. has daap_server => (
  8. is => 'rw',
  9. isa => 'AnyEvent::DAAP::Server',
  10. default => sub { AnyEvent::DAAP::Server->new(port => 13689, name => 'teto ' . hostname) },
  11. );
  12. __PACKAGE__->meta->make_immutable;
  13. no Mouse;
  14. ### XXX experimental. to enable this, run teto.pl with --enable_daap.
  15. # depends on https://github.com/motemen/AnyEvent-DAAP-Server
  16. # TODO
  17. # - preload tracks
  18. # - update track length
  19. sub BUILD {
  20. my $self = shift;
  21. $self->daap_server->setup;
  22. Teto::Playlist->meta->add_after_method_modifier(
  23. feed_url => sub {
  24. my $playlist = shift;
  25. $self->daap_server->add_track($_->as_daap_track) for $playlist->tracks;
  26. $self->daap_server->add_playlist($playlist->as_daap_playlist);
  27. $self->daap_server->database_updated;
  28. }
  29. );
  30. }
  31. package AnyEvent::DAAP::Server::Track::Teto;
  32. use Mouse;
  33. use Coro;
  34. extends 'AnyEvent::DAAP::Server::Track';
  35. has track => (
  36. is => 'rw',
  37. isa => 'Teto::Track',
  38. required => 1,
  39. );
  40. __PACKAGE__->meta->make_immutable;
  41. no Mouse;
  42. sub allow_range { 1 }
  43. sub write_data {
  44. my ($self, $connection, $res, $pos) = @_;
  45. my $track = $self->track;
  46. $track->log(debug => 'daap: start streaming');
  47. async {
  48. $Coro::current->{desc} = 'daap track play';
  49. $track->prepare;
  50. $track->play;
  51. };
  52. async {
  53. $Coro::current->{desc} = 'daap track write';
  54. until ($track->is_done) {
  55. $track->buffer_signal->wait;
  56. }
  57. my $buf = substr $track->buffer, $pos || 0;
  58. $res->content_length(length $buf);
  59. $res->content($buf);
  60. $self->push_response($connection, $res);
  61. };
  62. }
  63. package Teto::Track;
  64. sub dmap_track_id {
  65. my $self = shift;
  66. return $self->track_id & 0xFFFFFF; # XXX this field is 3 byte long
  67. }
  68. sub as_daap_track {
  69. my $self = shift;
  70. my $track = AnyEvent::DAAP::Server::Track::Teto->new(track => $self);
  71. $track->dmap_itemid($self->dmap_track_id);
  72. $track->dmap_containeritemid($self->track_id);
  73. $track->dmap_itemkind(2);
  74. $track->dmap_persistentid($self->track_id);
  75. $track->daap_songbitrate(192 * 1000);
  76. $track->daap_songsamplerate(44100 * 1000);
  77. $track->dmap_itemname($self->title || $self->track_id);
  78. $track->daap_songdateadded(time());
  79. $track->daap_songdatemodified(time());
  80. $track->daap_songformat('mp3');
  81. $track->daap_songsize($self->peek_buffer_length || (59 * 60 + 59) * 192 / 8);
  82. $track->daap_songtime((59 * 60 + 59) * 1000); # 59:59 dummy, to enable seek
  83. return $track;
  84. }
  85. package Teto::Playlist;
  86. use AnyEvent::DAAP::Server::Playlist;
  87. sub dmap_playlist_id {
  88. my $self = shift;
  89. return 0+$self & 0xFFFFFF;
  90. }
  91. sub as_daap_playlist {
  92. my $self = shift;
  93. my $playlist = AnyEvent::DAAP::Server::Playlist->new;
  94. $playlist->dmap_itemname($self->title);
  95. $playlist->dmap_itemid($self->dmap_playlist_id);
  96. $playlist->tracks([ map { $_->as_daap_track } $self->tracks ]);
  97. return $playlist;
  98. }
  99. 1;