Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tastebuds Radio - a rapidly developed Ember.js app

9b6423e15df69eef44d5d34189e128f3?s=47 Stephen Best
December 13, 2012

Tastebuds Radio - a rapidly developed Ember.js app

9b6423e15df69eef44d5d34189e128f3?s=128

Stephen Best

December 13, 2012
Tweet

Transcript

  1. Connect through music Thursday, 13 December 12

  2. What is Tastebuds? ? 2 Marriages (we know of) ‘A

    dazzlingly slick and intuitive site with enormous potential for growth’ 2 Million messages 100,000 users Meet people who share your taste in music Thursday, 13 December 12
  3. We’ve made some waves One of ‘10 Exciting European Startups

    from 2011’ TV COVERAGE Thursday, 13 December 12
  4. Our Users 18-25: 58% 26-30: 21% 31+: 21% 48% new

    to online dating Thursday, 13 December 12
  5. Brand Notoriety “Coldplay fans least likely to have sex on

    first date” The Sun, Metro, NME.com, Time.com 12,000 Facebook Likes “Worst album covers of all time” 800,000 Facebook Likes 12,000 Tweets Class, style, sophistication, taste - its nice to see all of these absent. @stephenfry Thursday, 13 December 12
  6. In the Wild Facebook HQ Menlo Park California Thursday, 13

    December 12
  7. Thursday, 13 December 12

  8. Team Julian Keenaghan Co-Founder / Developer Previously Senior Developer at

    Spoonfed Media. Guitarist. Alex Parish Co-Founder / Designer Previously Technical Director at Edelman & Spook Media. Bass Guitarist. Stephen Best Developer Previously Head of Technology at various London startups. Metal Bassist. Alex and Julian met in September 2007 as part of the instrumental rock group, ‘Years of Rice and Salt’. After incorporating Tastebuds in November 2010, they took part in the Springboard Accelerator programme in Cambridge in Summer 2011. Mariusz Bari Community Manager Previously Community Manager at LD50 music community. Electro DJ Thursday, 13 December 12
  9. Tastebuds on Spotify • Spotify app launched May 2012 covered

    by Wired.co.uk, Mashable, TechCrunch, Bloomberg TV, The Metro • Meet new people using the music you listen to on Spotify Thursday, 13 December 12
  10. Search Thursday, 13 December 12

  11. Events Thursday, 13 December 12

  12. Radio Thursday, 13 December 12

  13. What is Tastebuds Radio? A new, fun way to explore

    and interact with the profiles and song selections of your most musically compatible peers. • Scrolling list of matched users • Each face plays a song • Like • Comment Thursday, 13 December 12
  14. What is Tastebuds Radio? Quick Demo Thursday, 13 December 12

  15. Now the science part ... • Rails • Heroku •

    Postgres • MongoDB • Pusher Thursday, 13 December 12
  16. And Now .... Thursday, 13 December 12

  17. The Radio project began in late September Ember version //

    Last commit: 55d2d23 (2012-09-07 16:48:14 +0100) Design / planning ~ 1 week Prototype ~ 1 week Polished MVP ~ 1 week Feature complete ~ 2 weeks Thanks to Ember! Thursday, 13 December 12
  18. Thursday, 13 December 12

  19. 1 2 Buds = {} 3 window.Buds = Buds 4

    5 Buds.Controller = Em.ObjectController.extend() 6 Buds.ArrayController = Em.ArrayController.extend() 7 Buds.View = Em.View.extend() 8 Buds.CollectionView = Em.CollectionView.extend() 9 Buds.ContainerView = Em.ContainerView.extend() 10 Buds.Router = Em.Router.extend() 11 Buds.O = Em.Object.extend() 12 Buds.Mixin = Em.Mixin 13 Buds.Noop = Em.K 14 Base Classes This is your app first and an Ember app second. Define your own objects and APIs where possible Thursday, 13 December 12
  20. 2 3 Buds.Radio.Playlist = Buds.O.extend 4 profileSongs: {} 5 users:

    {} 6 orderedSongIds: [] 7 likeIds: [] 8 favouriteIds: [] 9 _cards: [] 10 _fetcher: null 11 12 init: -> 13 @_super() 14 @set('_fetcher', Buds.Radio.SongFetcher.create()) 15 16 fetchMoreSongs: (options) -> 17 @get('_fetcher').fetch(this, options) 18 19 getPlayer: (options) -> 20 throw new Error("Must provide a song to create pl 21 Buds.Radio.Player.create(options) 22 23 favouriteUser: (user) -> 24 return if @isUserFavourited(user) 25 @get('favouriteIds').pushObject(user.get('id')) 26 user.set("isFavourited", true) Core Objects • Playlist - Hides raw data - Gateway to other core objects Thursday, 13 December 12
  21. 1 2 Buds.Radio.Player = Buds.O.extend 3 song: null 4 htmlElementId:

    'budsPlayer' 5 isPlaying: null 6 scrobbler: null 7 songEndedCallback: Buds.Noop 8 9 init: -> 10 @_super() 11 @set('scrobbler', Buds.Radio.Scrobbler.create(son 12 @set('viewLogger', Buds.Radio.Player.ViewLogger.c 13 22 insertFlashElement: -> 23 unless @get('song') 24 throw new Error("must set song before inserting 25 31 embedUrl = "http://www.youtube.com/apiplayer?vide 32 window.onYouTubePlayerReady = => @youTubeReady.ap 33 window.swfobject.embedSWF(embedUrl, "yt-player", 34 35 play: -> 36 @get('htmlElement').playVideo() 37 38 pause: -> 39 @get('htmlElement').pauseVideo() Core Objects • Playlist - Hides raw data - Gateway to other core objects • Player - Handles playstate - Abstracts YouTube API Thursday, 13 December 12
  22. 1 2 Buds.Radio.Player = Buds.O.extend 3 song: null 4 htmlElementId:

    'budsPlayer' 5 isPlaying: null 6 scrobbler: null 7 songEndedCallback: Buds.Noop 8 9 init: -> 10 @_super() 11 @set('scrobbler', Buds.Radio.Scrobbler.create(son 12 @set('viewLogger', Buds.Radio.Player.ViewLogger.c 13 22 insertFlashElement: -> 23 unless @get('song') 24 throw new Error("must set song before inserting 25 31 embedUrl = "http://www.youtube.com/apiplayer?vide 32 window.onYouTubePlayerReady = => @youTubeReady.ap 33 window.swfobject.embedSWF(embedUrl, "yt-player", 34 35 play: -> 36 @get('htmlElement').playVideo() 37 38 pause: -> 39 @get('htmlElement').pauseVideo() Core Objects • Playlist - Hides raw data - Gateway to other core objects • Player - Handles playstate - Abstracts YouTube API Card ( user info / pictures ) SongFetcher Scrobbler / ViewLogger Song/Artist/User Factories Thursday, 13 December 12
  23. Thursday, 13 December 12

  24. 1 Buds.Radio.Router = Buds.Router.extend 2 3 showSong: (router, event) ->

    4 router.transitionTo('show', event.context) 5 6 skipSong: (router, event) -> 7 song = event.context 8 Buds.Radio.SkipLogger.create({ song: song }).log() 9 router.send('nextSong', event) 10 11 nextSong: (router, event) -> 12 currentSong = event.context 13 nextSong = App.playlist.nextSong(currentSong) 14 15 if nextSong instanceof Buds.Radio.NoMoreSongsError 16 router.send('fetchMoreCards', callback:-> 17 router.send('nextSong', event) 18 ) 19 else 20 router.transitionTo('show', nextSong) 21 The Router • Actions Thursday, 13 December 12
  25. 29 likeSong: (router, event) -> 30 song = event.context 31

    App.playlist.likeSong(song, "radio_bottom") 32 33 favouriteUser: (router, event) -> 34 user = event.context 35 App.playlist.favouriteUser(user) 36 37 fetchMoreCards: (router, options) -> 38 return if router.get('cardsController').get('loading', true 39 40 options = options || {} 41 callback = options.callback || Buds.Noop 42 43 router.get('cardsController').set('loading', true) 44 App.playlist.fetchMoreSongs(callback: -> 45 router.get('cardsController').set('loading', false) 46 callback.apply(window) 47 ) The Router • Actions Thursday, 13 December 12
  26. 49 root: Em.Route.extend 50 root: Em.Route.extend 51 route: '/' 52

    connectOutlets: (router) -> 53 applicationController = router.get('applicationControll 54 applicationController.connectOutlet 55 outletName: 'player' 56 name: 'player' 57 58 applicationController.connectOutlet 59 outletName: 'userProfile' 60 name: 'userProfile' 61 62 applicationController.connectOutlet 63 outletName: 'playerControls' 64 name: 'playerControls' 65 66 applicationController.connectOutlet 67 outletName: 'cards' 68 name: 'cards' 69 The Router • Actions • Setup Thursday, 13 December 12
  27. 69 70 index: Em.Route.extend 71 route: '/' 72 connectOutlets: (router)

    -> 73 firstSong = App.playlist.get("firstSong") 74 router.transitionTo('show', firstSong) 75 76 show: Em.Route.extend 77 route: '/:id' 78 connectOutlets: (router, context) -> 79 if context instanceof Buds.ProfileSong 80 song = context 81 else 82 song = App.playlist.getSong(context.id) 83 The Router • Actions • Setup • Routes Thursday, 13 December 12
  28. 84 85 applicationController = router.get('applicationController') 86 applicationController.connectOutlet 87 outletName: 'userProfile'

    88 name: 'userProfile' 89 context: song.get("user") 90 91 player = App.playlist.getPlayer 92 song: song 93 songEndedCallback: -> router.send("nextSong", context: song) 94 95 @set('player', player) 96 97 applicationController.connectOutlet 98 outletName: 'player' 99 name: 'player' 100 The Router • Actions • Setup • Routes • Connect outlets Thursday, 13 December 12
  29. 100 101 router.get('cardsController').set('currentSong', song) 102 router.get('playerController').set('content', song) 103 router.get('playerController').set('player', player)

    104 router.get('playerControlsController').set('content', song) 105 router.get('playerControlsController').set('player', player) 106 107 router.get('userProfileController').set('isLoggedIn', App.isLo 108 router.get('playerController').set('isLoggedIn', App.isLoggedI 109 router.get('playerControlsController').set('isLoggedIn', App.i 110 111 buds.helper.replaceTitle(song.get("title")) The Router • Actions • Setup • Routes • Connect outlets • Pass data Thursday, 13 December 12
  30. Controllers and Views Often tightly coupled and therefore I like

    to keep them together in xxx_controller_view.coffee No Rails 3.1 asset pipeline so we put templates in server-side partials and rendered with the page. (not ideal but quick to develop) Thursday, 13 December 12
  31. Controller Responsibility Receives data from router Data transformation (like a

    presenter) Sends messages to router (outside context of the action helper) Thursday, 13 December 12
  32. Thursday, 13 December 12

  33. View Responsibility Events from DOM Micro-state - selected item -

    scroll position - hover state Activate legacy / jQuery plugins - Call functions / plugins on didInsertElement Thursday, 13 December 12
  34. Stuff that was hard Interaction with one view must tell

    another view to resize - We sent a message via the view -> controller -> router -> other controller -> other view Dependency Injection vs contentBindings Integrating with legacy jQuery code / plugins Thursday, 13 December 12
  35. Stuff I would recommend Server responsibility Initial state ( javascript

    and HTML assets ) Persistence ( JSON API ) Minimise coupling to Ember - Use your own classes - Define your own APIs - Keep your logic out of the router - Make porting your app possible Thursday, 13 December 12
  36. ¿ ¿ ¿ Questions ? ? ? Thursday, 13 December

    12
  37. Thursday, 13 December 12