Satisfy the market or leave it

I had the first Pebble watch from a company who was the first to raise $10M on Kickstarter and then the first to raise $20M with their second watch. Few years later this seemingly popular and successful company with all their assets, intellectual property and strong interest first had few layoffs eventually was sold for just $23M to Fitbit. How did that happen?

OnePlus-Pebble.png

It's not because they got fierce competition or were too early to the market - smart watches from big brands weren't there yet and the wearables sector had healthy demand already as well. The problem was that Pebble tried so hard to please their core audience - early adopters that their products never came out from serving that niche to catch the broader market.

Early adopters are a good way of taking your product off the ground: your customers are willing to pay extra to be the first to get your product even if it means dealing with the potential production delay which is common on Kickstarter. But the early adopters are ready to sacrifice their time and money for that new thing on the market and are also much more forgiving to any imperfections and roughness of product's first versions than any regular user would be.

The problem comes after. Since the early adopters is the first and smallest user group in the product's life cycle, holding on to them will never let you move forward and gather necessary resources to do so. After getting your product, early adopters can't wait you to make it better usually for no extra cost. Moreover, each backer thinks his or her new idea/feature should definitely be implemented to the next revision of the product and also retain all previous features as well.

Company's early adopters probably also won't jump into buying the next revision of their product considering the changes aren't significant enough to update and demanding unlimited support for the first version. Also paying a full extra price for something which is similar to what you have (usual iteration of products) especially with similar features is not what early adopters do.

And by sticking to the requests of those first customers, treasuring them companies continue building products which almost no one would buy - for previous customers there won't be much better/newer than what they have, and for the mass market the upcoming products without major changes will be still too finicky, techy and not very appealing. That's why companies which don't adapt to the new users by trying to satisfy the first ones go out from business just not reaching that bigger mass market group.

In this video the author explains the situation in more details with examples that include Pebble's. He also touches OnePlus which for me is the most interesting one and where I myself have proof of the early adopters theory. A friend of mine who is a strong OnePlus supporter is very judgmental about OnePlus' moves in the recent years. For him it's unacceptable for the company leaving out the headphone jack, raising the prices and overall following the industry leaders - everything opposite the brand was famous for when it was listening to their users' opinions back in the day.[1]

But at the same time my friend bought only 2 out of OnePlus' 9 models (ignore the 10th McLaren 6T) literally supporting with money his favorite company only twice! No wonder they had to adapt to the mass market and leave the core loyal fans behind. I bet Apple is successful just because except of pure specs they are selling the image of being an iPhone user so successfully that those users fuel up the company with yearly (2 years max) upgrades. This is where OnePlus is now going and where every other company which overcame the first stage should go to stay in business. Otherwise by sticking to the early adopters you won't be able to ramp up to the next stages.


  1. At some point OnePlus actually had a poll whether to include or not the headphone jack. Unfortunately the poll was ignored and the 6T was released without a headphone jack. β†©οΈŽ

Podcast generator / getid3 issue with php 7.1

Yesterday my belowed Podcast generator (where I'm hosting private audio recordings to listen to conveniently) had an issue. After uploading an episode its web interface was stuck at a blank white page and the episode was never added (though the file was actually uploaded).

ID3

After looking into nginx's error logs (nano /var/log/nginx/error.log on Ubuntu) I found this PHP Error:
Fatal error: Uncaught Error: [] operator not supported for strings in getid3/module.audio.mp3.php(1088): getid3_mp3->decodeMPEGaudioHeader

Previously, running all the same on PHP 7.0 had no issues whatsoever. But when I moved the podcast generator to another server, it had PHP 7.1 and that what caused the issue.

Thankfully I was able to overcome it by updating all the files in the getid3/ folder of the web app with a newer version of getid3 (responsible for generating id3 tags from audiofiles). So if you have the problem with podcast generator or getid3 in any other app, now you know how to fix it πŸ™‚

Tracking AVPlayer starting stream playback

If you ever worked with streaming audio on iOS you definitely used AVAudioPlayer or AVPlayer as one of the low-hanging and robust options provided by Apple.

AVPlayer

Outside of the stellar performance and versatility the problem with both of them is a weak delegation system, especially for medium/pro use. It's true that you can go far with AVPlayer and even use its basic status changes to react to its behavior but they have few limitations:

  1. You actually have to work with them via KVO (Key-Value Observation) which is not ideal
  2. KVO still doesn't give you answers to everything you need

I had this basic need for a while: do some stuff in the app once the AVPlayer starts playing music from a stream. KVO'ing 'status', 'rate' and 'currentItem.status' was advised everywhere but none of them worked properly. And relying on AVPlayer's 'readyToPlay' status was incorrect as well - the player changes its status to it as soon as possible and not when actually starting playing the stream.

Thankfully I got to know about another key to look after, the 'currentItem.loadedTimeRanges'. And here's how you use it:

First, you add the observer (and then don't forget to remove it at the end of the player's lifecycle!):

avPlayer.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)

And then you just observe it!

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        if object as? AVPlayer === avPlayer && keyPath == "currentItem.loadedTimeRanges" {
            if let timeRanges = avPlayer.currentItem?.loadedTimeRanges, timeRanges.count > 0 {
                let timeRange = timeRanges.first as! CMTimeRange
                let currentBufferDuration = CMTimeGetSeconds(CMTimeAdd(timeRange.start, timeRange.duration))
                let duration = avPlayer.currentItem!.asset.duration
                let seconds = CMTimeGetSeconds(duration)
                
                if currentBufferDuration > 2 || currentBufferDuration == seconds {
                    delegate.startedPlayingStream()
                }
                
            }
}

Next the player calls its delegate when the stream actually started playing. Neat! πŸ™‚

How to search in Firebase

In case you've been wondering (I was) how to seach for stuff in Firebase's Cloud Firestore I found it randomly today and would like to share it:

Firebase-search

How to increase upload file size limit

I'm using a self-hosted podcast web app (PodcastGenerator) to serve and listen audiobooks in Overcast with Smart Speed and position tracking. In order to get new books in my podcast feed I upload them via the built-in web admin panel. But since the audiobook files can be as large as 200-300MB, uploading them via a browser hits the default upload file limit of both Nginx (my http server) and PHP (which my podcast web app uses) pretty soon.

Increase-upload-file-size-limit

In order to increase the upload file size limit, do this:

  1. Edit Nginx's config
    sudo nano /etc/nginx/nginx.conf
    and add client_max_body_size 2000M; into the 'http' block
    Save and exit

  2. Edit php's config
    sudo nano /etc/php/7.1/fpm/php.ini (or track down your php.ini file by creating a *.php file with contents and run it)
    Find and change upload_max_filesize and post_max_size as well to = 2000M

  3. Finally restart Nginx (or your http server) via
    service nginx reload
    and also you can restart php to be safe via
    service php7.0-fpm restart

All my commands are meant for Ubuntu I'm running, for your OS you'll have use your own alternatives.

Happy uploading! πŸ™‚

It's not worth securing in-app purchases

Few years ago, while working on one iOS app we had a problem with in-app piracy. At that time there were and now there are several ways of keeping track of users' purchases on your own server and validate each of them to stop pirates of using your app's unlockable features with their faked unauthorized purchases.

In-apps

But I remember it was a hassle to build such a validating system. And I also remember reading about even bigger companies discontining their own billing systems like that because it was not worth the hassle for dealing and supporting 95% fair users and their purchases because of 5% scammers.

In order to hack in-apps, your iPhone has to be jailbroken. Then you can use a manual like this to get an in-app for free. The latest info on the amount of jailbroken iPhones I found was from the end of 2016 with only 0.4% of iOS devices being jailbroken which might have the potential to hack anything. From what I’ve read and heard over the years that percentage may be even lower now as less and less people jailbreak, because the main reason for it (I can confirm myself) was to get new functionality. And with the latest few iOS updates we got so much new stuff, there are almost no reasons left to jailbreak, at least not for the new features.

iOS devs still can pass receipts and validate them on servers, but what would be the main reason for it? Receipts are passed and then matched for non-consumable purchases: the user unlocked a part of the functionality once and then when the user restores it, his receipt is matched with the receipt stored on the server to avoid unauthorized functionality unlocks. So if someone can fake unlocking non-consumables you either can build a wall around it... or just give it away. If a person tries to hack your purchase - he's definitely not going to buy it, so just embrace it! Replace a mandatory in-app with free with ads model, or offer even more compelling features in your app and introduce non-hackable subscriptions!

If you sell consumables - even better, there's even less reason to secure them. A person who buys a consumable uses it right away, essentially not leaving much to himself anyway.

As for Android - that’s another story. On Android I found at least one, two and three articles on ways how to hack in apps even without root (analog of jailbreaking).

Fix AirPrint -9923 scanner error on MacOS Mojave

After upgrading to MacOS Mojave last autumn I actually had no problem using my Brother DCP-L2532DW via AirPrint. But everything changed when 10.14.3 rolled out few weeks ago.

AirPrint

My Mac did see the printer on the network but when I tried to scan it gave me the -9923 error and obviously nothing happened afterwards.

The solution was actually simple: to turn off IPv6 in my printer's settings, switching it to IPv4 only. If your printer doesn't have settings, try adjusting your router's settings to disable IPv6 and have each wireless devices (including the printer) to obtain an IPv4-only IP address.

Now I wonder when IPv6 becomes reliable enough for regular household items πŸ™‚

How to merge mp3s on Mac

In case you have a bunch of mp3 files (chapters of an audiobook for example) which you want to merge and keep or listen as a single file here's a quick hint for you:

  1. First you open Terminal and cd to the folder with files
  2. Then you just type `cat file1.mp3 file2.mp3 file3.mp3 > outputfile.mp3' and hit Enter!

Merge-mp3

You will get a big mp3 file which unfortunately will have an incorrect duration from the first file of the batch. But you can easily fix that with ffmpeg or other utilities. I prefer a small GUI apps without installing any other libraries. My app of choice is the MP3 Scan + Repair App. You just open the merged mp3 file with it and push the wrench/fix button. That's about it!

Apple's March Keynote Summary

What has been announced:

Apple News+ - $9.99 subscription for accessing 300+ paper-to-digital magazines, including LA Times and WSJ.

Apple-March-Keynote

Apple Card - virtual credit card with real daily cash cash-backs and a physical credit card made out of titanium 😍 They are actually having nice promises about the low transaction and international (really interesting) fees.

Apple Arcade - gaming subscription with 100+ iOS/Mac/AppleTV exclusive games. Details and pricing info will be later.

Apple TV - new app, with better suggestions, integrations with all the content providers. Soon available on smart TVs as well.

Apple TV+ ad-free, on demand subscription with unique and exclusive content. More details soon.

Personally I was waiting for better iCloud Storage options πŸ™‚ But overall it was a good and very (next level) emotional keynote of all. The guests made the second half of the keynote very heart-warming.

Fixing wife's headphones

My wife has the OG Beats Studio headphones which were produced even before Apple acquired Beats in 2014. The Studios have good battery life and work great by this day except their earpads didn't last as good:

Old-pads

I was asked to fix them and this is what I did. I went to ebay and ordered earpads copies for the presumably Beats Studio 1.0 from China. But when they arrived a month after, it turned out they are incompatible with the Studios because of different, incompatible frames:

Incompatible-frames

The Studios' frames were a bit different shape and with different latches. Also after tearing the worn earpads it looked like the Studio's frames aren't even removable and were glued to the headphones. Not having other options except throwing away the old earpads and the incompatible new ones I got an idea.

I removed the frames from the new earpads by breaking the frames and taking them out of the earpads' cushions. Then I just started pulling on one cushion onto the Studios' frame and being flexible it actually fitted around the old frame very well! After finishing out the first earpad, I repeat the same with the second and it worked! Now my wife has hear headphones as good as new:

Old-pads